Java-SpringBoot:员工管理系统
Java:SpringBoot-员工管理系统
参考的代码和资源链接:https://blog.csdn.net/qq_45173404/article/details/108934414?spm=1001.2014.3001.5501
学习视频:B站 狂神说Java – https://www.bilibili.com/video/BV1PE411i7CV
学习文档: 微信公众号 狂神说 –https://mp.weixin.qq.com/mp/homepage?__biz=Mzg2NTAzMTExNg==&hid=1&sn=3247dca1433a891523d9e4176c90c499&scene=18&uin=&key=&devicetype=Windows+10+x64&version=63020170&lang=zh_CN&ascene=7&fontgear=2
SpringBoot回顾
SpringBoot其实就是去整合了Spring、spring mvc框架,整合了里面的配置信息,“约定大于配置”。
xxxAutoConfiguration 完成自动配置文件,给容器中导入非常多的自动配置类。即给容器中导入配置好的组件 bean信息。
使用时可以直接拿来使用,开箱即用。【不用再向原来mybatis、spring中那样写xml配置文件了】
内嵌了服务器,比如Tomcat、Jetty。简化了web项目开发,不用再自己启动配置tomcat
单体应用架构转化为微服务架构风格。
最重要的就是 SpringBoot自动装配原理了。
@SpringBootApplication
public class Springboot03WebApplication {public static void main(String[] args) {SpringApplication.run(Springboot03WebApplication.class, args);}
}
开启SpringBoot应用的注解是 @SpringBootApplication,它里面包括:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {......
}
- @ComponentScan 的作用是指定包加入Spring IOC的包扫描,自动扫描并加载符合条件的bean 或组件。将bean定义加载到 IOC容器中。
- @SpringBootConfiguration 的作用是 表明这个类是springboot的配置类,里面保存的是配置信息,对应于spring中的xml配置文件
- @EnableAutoConfiguration :开启自动配置功能。原来我们需要自己去添加配置的东西,在SpringBoot中可以直接自动地帮我们配置。
配置文件:
自己定义 *.yaml 或者 *.properties 配置文件。然后可以去修改SpringBoot中默认的配置信息。
yaml语法:key: 空格 value
server:port: 8081 #选择要激活那个环境块 spring:profiles:active: prod--- server:port: 8082 spring:profiles: dev # 设置环境的名称--- server:port: 8084 spring:profiles: prod # 设置环境的名称person:name: alznage: 18happy: truebirth: 2002/01/01maps: {al: last name, zn: first name}lists:- learning- running- dancingdog:name: xiaohuage: 6
- @ConfigurationProperties(加载配置文件的注解)对属性赋值
- **@PropertySource :**加载指定的配置文件;
利用注解开发自定义配置文件,
- 如mvc框架中的视图解析器:ViewResolver
- 将自定义的配置文件信息 注入到Bean中,由SpringBoot帮我们去装配,组合所有的配置信息
// 如果你想要 div 一些定制的功能,只要写这个组件,然后把它交给springboot,springboot就会帮我们装配 // 扩展 springmvc 围绕着核心 dispatchservlet @Configuration public class MyMvcConfig implements WebMvcConfigurer {// ViewResolver 实现了视图解析器接口的类。 我们就可以把这个当作视图解析器@Bean //放到bean中。通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!public ViewResolver myViewResolver(){return new MyViewResolver();}//我们写一个静态内部类,视图解析器就需要实现ViewResolver接口private static class MyViewResolver implements ViewResolver{@Overridepublic View resolveViewName(String s, Locale locale) throws Exception {return null;}} }
thymeleaf模板引擎:同样也是为了简化web开发,直接继承 thymeleaf模板引擎。
模板引擎的作用就是帮助我们写了一个页面模板,比如有些值是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。
在使用SpringBoot进行开发的时候,自己要解决的、参与的工作是:
- 导入静态资源。配置资源文件:*.properties、 *.yaml
- jsp、thymeleaf模板引擎。便于简化开发,动态的资源数据信息,代码去提供。thymeleaf模板引擎提供一个固定的页面
- 装配和扩展 SpringMVC。自己定义的关于 WebMvcConfigurer 的mvcConfig配置信息;SpringBoot中包含的默认mvc配置信息。
@EnableWebMvc // 这个就是导入了一个类: DelegatingWebMvcConfiguration: 从容器中获取所有的 webmvcConfig
@EnableWebMvc 将 WebMvcConfigurationSupport 组件 导入进来了;而导入的 WebMvcConfigurationSupport 只是SpringMVC最基本的功能!此时我们全面接管SpringMVC。
SpringBoot:员工管理系统
SpringBoot web开发
jar:webapp
自动装配:
- 创建应用,选择模块
Springboot到底帮我们配置了什么? 我们能不能进行修改? 能修改哪些东西?能不能扩展?
xxxxAutoConfigurartion:自动配置类;给容器中添加组件
xxxxProperties: 封装配置文件中相关属性;
其实SpringBoot的东西用起来非常简单,因为SpringBoot最大的特点就是自动装配。
使用SpringBoot的步骤:
1、创建一个SpringBoot应用,选择我们需要的模块,SpringBoot就会默认将我们的需要的模块自动配置好
2、手动在配置文件中配置部分配置项目就可以运行起来了
3、专注编写业务代码,不需要考虑以前那样一大堆的配置了。
我们在利用 Springboot的时候,能够利用和修改哪些配置,以及增加配置文件呢? 如下所示,我们最终是要通过这两个去进行 环境配置:
- 向容器中自动配置组件 :xxxAutoconfiguration
- 自动配置类,封装配置文件的内容:xxxProperties
我们在进行 springboot web 开发要解决的问题:
- 导入静态资源
- 首页
- jsp、模板引擎Thymeleaf
- 装配扩展 SpringMVC
- 增删查改
- 拦截器
- 国际化
参考的代码和资源链接:https://blog.csdn.net/qq_45173404/article/details/108934414?spm=1001.2014.3001.5501
1、环境搭建
新建一个SpringBoot项目:springboot-03-system。
选择pom依赖文件:SpringWeb 和Thymeleaf模板引擎
导入静态资源
创建不存在的静态资源目录 public 和 resources,便于添加我们想要自定义的静态资源。
SpringBoot中的静态资源导入:【SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面】
- 两种映射规则:“classpath:/META-INF/resources/”、“classpath:/resources/”、“classpath:/static/”、“classpath:/public/”
- 自定义静态资源路径
所有的 /webjars/** , 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源
静态资源文件:
把静态资源文件导入到对应文件夹中:
- 将 html 静态资源放置 templates 目录下
- 将 asserts 目录下的 css、img、js 等静态资源放置 static 目录下
数据库的创建
创建一个伪数据库,进行数据库的模拟。
导入lombok,便于使用注解开发:
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
实体类:pojo
创建实体类:Department 部门表
package com.al.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;// 部门表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {private Integer id;private String departmentName;
}
员工表:
- 设置一对多和多对一。员工和部门的关系。
- 性别没有使用char类型,使用Integr
package com.al.pojo;import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;// 员工表
@Data
@NoArgsConstructor
public class Employee {private Integer id;private String lastName;private String email;private Integer gender; // 0:女 1:男。 用数字表示性别男女private Department department;private Date birth;public Employee(Integer id, String lastName, String email, Integer gender, Department department, Date birth) {this.id = id;this.lastName = lastName;this.email = email;this.gender = gender;this.department = department;// 默认的创建日期//this.birth = birth;this.birth = new Date();}
}
dao层
创建dao层:dao层,负责底层数据的操作。
Bean的注解开发:
@Component 通常是通过类路径扫描来⾃动侦测以及⾃动装配到Spring容器中。@Component 表明会将这个类交给 Spring管理装配,它与@Bean的不同之处在于,它作用在类上。注册一个类的实例到Spring IOC 容器。需要的时候,从里面去获取。
为了更好的进行分层,在 web 开发中,会按照 mvc 三层架构分层。Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
- @Controller:web层
- @Service:service层
- @Repository:dao层
Bean的自动装配配置:
- @Autowired: 自动装配 通过类型,名字
- 如果@Autowired 不能唯一自动装配属性,则还需要通过 @Qualifier(value = “xxx”)
- @Nullable: 字段标注了这个注解,表示这个字段可以为 null
- @Resource:自动装配 通过名字, 类型。先去匹配byName,后去进行byType。
部门dao类:
DepartmentDao:将此类注入到Spring IOC容器中,dao层使用注解 @Repository。
模拟数据库中的部门数据。采用map表示。
Map<Integer, Department> departments; // Integer代替数据库表id,Department表示数据库表中部门的具体信息。
package com.al.dao;import com.al.pojo.Department;
import org.springframework.stereotype.Repository;import java.util.Collection;
import java.util.HashMap;
import java.util.Map;// 部门dao
@Repository
public class DepartmentDao {// 模拟数据库表中的数据private static Map<Integer, Department> departments = null;static {// 创建一个部门表departments = new HashMap<>();departments.put(101, new Department(101, "户部"));departments.put(102, new Department(102, "兵部"));departments.put(103, new Department(103, "吏部"));departments.put(104, new Department(104, "刑部"));departments.put(105, new Department(105, "礼部"));departments.put(106, new Department(106, "工部"));}// 获取所有的部门信息public Collection<Department> getDepartments(){return departments.values();}// 通过id获取部门信息public Department getDepartmentById(Integer id){return departments.get(id);}
}
员工dao类:
EmployeeDao类:需要去继承DepartmentDao 这个部门dao,获取这样的一个类实例,所以
使用注解@Autowired 获取DapartmentDao的一个实例。
模拟数据库中的员工表数据。采用map表示:private static Map<Integer, Employee> employees = null;
package com.al.dao;import com.al.pojo.Department;
import com.al.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;// 员工Dao
@Repository
public class EmployeeDao {// 模拟数据库表中的员工数据信息private static Map<Integer, Employee> employees = null;// 员工所属部门@Autowiredprivate static DepartmentDao departmentDao;static {employees = new HashMap<>();employees.put(1001, new Employee(1001, "AAA", "1356207897@qq.com", 1, new Department(101, "户部"), new Date()));employees.put(1002, new Employee(1002, "BBB", "1356207897@qq.com", 0, new Department(102, "兵部"), new Date()));employees.put(1003, new Employee(1003, "CCC", "1356207897@qq.com", 1, new Department(103, "吏部"), new Date()));employees.put(1004, new Employee(1004, "DDD", "1356207897@qq.com", 0, new Department(104, "刑部"), new Date()));employees.put(1005, new Employee(1005, "EEE", "1356207897@qq.com", 1, new Department(105, "礼部"), new Date()));}// 主键自增private static Integer initId = 1006;// 增加一个员工public void addEmployee(Employee employee){if (employee.getId() == null){employee.setId(initId++);}employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));employees.put(employee.getId(), employee);}// 查询所有员工信息public Collection<Employee> getAll(){return employees.values();}// 通过id查询员工信息public Employee getEmployeeById(Integer id){return employees.get(id);}//通过id删除员工public void deleteEmployeeByID(int id) {employees.remove(id);}
}
2、首页实现
首页实现:自定义配置类 MyMvcConfig
首页控制,使导入的templates中的静态资源有效。利用该自定义的静态资源,需要提供静态资源配置文件。
- 在 templates目录下的所有页面,只能通过 controller来跳转。这个需要模板引擎的支持。如 thymeleaf。
在控制器 controller中,进行首页控制,IndexController:
package com.al.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
public class IndexController {@RequestMapping({"/", "/index.html"}) //这是一个集合public String index(){return "index";}
}
不过最好我们采用这种方式去进行对根目录下的访问:【采用自定义配置 资源文件的方式】
在主程序同级目录下新建 config 包用来存放自己的配置类:
- 新建自定义配置类 MyMvcConfig,进行视图跳转
package com.al.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class MyMvcConfig implements WebMvcConfigurer {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/").setViewName("index");registry.addViewController("/index.html").setViewName("index");}
}
启动浏览器测试,观察界面:界面可以访问,但是不理想
想要直接去访问 template 文件夹路径下的静态资源,需要引入模板引擎。这里采用 SpringBoot中的 thymeleaf 。
- 导入thymeleaf的依赖
- 在 index中就需要导入 thymeleaf
- 语法要使用thymeleaf语法,URL路径要用@{…}
回顾:静态资源映射规则
创建有效的静态资源路径 文件夹:而其中的classpath:/META-INF/resource/ 这个就对应着上面的那个webjars 资源路径。
"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
classpath对应的文件夹等级就是 resources 资源文件夹。
我们创建以下的几个文件夹,都能够存放我们的静态文件:【可以直接获取该路径下的静态资源文件】
首页实现:thymeleaf模板引擎
上述测试的时候,发现页面中的图片没有加载出来,且css和js的样式并没有显示。 这是因为 想要直接去访问 template 文件夹路径下的静态资源,需要引入模板引擎。
在SpringBoot中,推荐使用Thymeleaf
作为模板引擎,我们将其中的语法改为Thymeleaf
,所有页面的静态资源都需要使用模板引擎接管,里面的动态数据由后端提供。前面页面需要符合 thymeleaf语法。
Selection Variable Expressions: *{…}:选择表达式:和${}在功能上是一样;
Message Expressions: #{…}:获取国际化内容
Link URL Expressions: @{…}:定义URL;
Fragment Expressions: ~{…}:片段引用表达式
在index.html页面中:
引入thymeleaf:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
第一个
/
代表项目的classpath,也就是这里的resources
目录。 直接获取 “classpath:/static/” 路径下的资源文件:thymeleaf语法中 定义URL的方式:@{}
index.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><meta name="description" content=""><meta name="author" content=""><title>Signin Template for Bootstrap</title><!-- Bootstrap core CSS --><link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"><!-- Custom styles for this template --><link th:href="@{/css/signin.css}" rel="stylesheet"></head><body class="text-center"><form class="form-signin" action="dashboard.html"><img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72"><h1 class="h3 mb-3 font-weight-normal">Please sign in</h1><label class="sr-only">Username</label><input type="text" class="form-control" placeholder="Username" required="" autofocus=""><label class="sr-only">Password</label><input type="password" class="form-control" placeholder="Password" required=""><div class="checkbox mb-3"><label><input type="checkbox" value="remember-me"> Remember me</label></div><button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button><p class="mt-5 mb-3 text-muted">© 2017-2018</p><a class="btn btn-sm">中文</a><a class="btn btn-sm">English</a></form></body></html>
测试访问结果:
其它页面进行同样的修改。
配置首页的时候:可以在这里自己自行定义 目录, 此时 localhost:8080失效,必须是 localhost:8080/al
- application.properties 配置资源文件:
server.servlet.context-path=/al
此时的测试结果:ulr请求路径必须是:http://localhost:8080/al/
3、页面国际化
IDEA中编码设置:UTF-8
首先在 setting -> FileEncodings中 必须要确保编码为 UTF-8, 不然写的东西会乱码。
i18n国际化资源配置文件
在静态资源中创建一个 i18n文件,国际化:
在这其中分别创建两个配置文件:
命名方式是下划线的组合:文件名
_
语言_
国家.properties;以此方式命名,IDEA会帮我们识别这是个国际化配置包,自动绑定在一起转换成如下的模式:
login.properties 和 login_zh_CN.properties, 然后IDEA会自动帮你合并:
如果再添加其它配置文件:选择Resource Bundle ‘login’,右键点击添加即可:
此时只需要输入区域名即可创建成功,比如输入 en_US,就会自动识别
此时的i18n文件下的配置资源文件:
在编写login资源配置文件时:可以选择左下角的 Resource Bundle进行可视化编写:
- 进入到可视化编辑页面后,点击加号 + ,添加属性,首先新建一个 login.tip 代表首页中的提示
然后对这种提示,去分别做语言配置:在输入框中写入相应的语言提示
login.properties:
login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
login_zh_CN.properties:
login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
login_en_US.properties:
login.btn=login
login.password=password
login.remember=remember
login.tip=Please sigin in
login.username=suaername
那么如何让我们配置的这些生效呢?
在Spring程序中,国际化主要是通过ResourceBundleMessageSource这个类来实现的
Spring Boot通过MessageSourceAutoConfiguration为我们自动配置好了管理国际化资源文件的组件
查看源码MessageSourceAutoConfiguration,
- 寻找Meaasge, 找到它的前缀。@ConfigurationProperties(prefix = “spring.messages”)
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {private static final Resource[] NO_RESOURCES = {};@Bean@ConfigurationProperties(prefix = "spring.messages")public MessageSourceProperties messageSourceProperties() {return new MessageSourceProperties();}@Beanpublic 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;}...
}
关于 messageSource 方法中的参数:MessageSourceProperties properties。
MessageSourceProperties 类:
public class MessageSourceProperties {/*** Comma-separated list of basenames (essentially a fully-qualified classpath* location), each following the ResourceBundle convention with relaxed support for* slash based locations. If it doesn't contain a package qualifier (such as* "org.mypackage"), it will be resolved from the classpath root.*/private String basename = "messages";/*** Message bundles encoding.*/private Charset encoding = StandardCharsets.UTF_8;...
}
注释的意思:
* 逗号分隔的基本名称列表(本质上是完全限定的类路径 location),每个都遵循 ResourceBundle 约定,并轻松支持
*基于斜线的位置。 如果它不包含包限定符(例如"org.mypackage"),它将从类路径根目录中解析。
意思是:如果你不在springboot配置文件中指定以.分隔开的国际化资源文件名称的话,它默认会去类路径下找messages.properties作为国际化资源文件。
自定义国际化资源文件,因此我们需要在SpringBoot配置文件application.properties中加入以下配置指定我们配置文件的名称
- 我们对配置文件的真实位置(i18n.login),修改后:
#自定义配置:国际化资源文件
spring.messages.basename=i18n.login
Message Expressions: #{…}:获取国际化内容
对于国际化 message如何写:thymeleaf 中 表示message 的语法为 #{…}
<label><input type="checkbox" value="remember-me" th:text="#{login.remember}"><input type="checkbox" value="remember-me"> [[#{login.remember}]]</label>
修改的地方:【对应着前端页面显示的地方】
此时的index.html 代码:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><meta name="description" content=""><meta name="author" content=""><title>Signin Template for Bootstrap</title><!-- Bootstrap core CSS --><link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"><!-- Custom styles for this template --><link th:href="@{/css/signin.css}" rel="stylesheet">
</head><body class="text-center">
<form class="form-signin" action="dashboard.html"><img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72"><h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1><label class="sr-only">Username</label><input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus=""><label class="sr-only">Password</label><input type="password" class="form-control" th:placeholder="#{login.password}" required=""><div class="checkbox mb-3"><label>
<!-- <input type="checkbox" value="remember-me" th:text="#{login.remember}"> Remember me--><input type="checkbox" value="remember-me" th:text="#{login.remember}"></label></div>
<!-- <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>--><button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.btn}]]</button><p class="mt-5 mb-3 text-muted">© 2017-2018</p><a class="btn btn-sm">中文</a><a class="btn btn-sm">English</a>
</form></body></html>
此时的测试:首页:
国际化组件的中英文切换
切换方式
如何通过按钮 进行中英文的转换?在index.html
页面中可以看到两个标签:
<a class="btn btn-sm">中文</a><a class="btn btn-sm">English</a>
在标签中加入跳转链接,点击后进行响应跳转,获取对应的信息资源参数:
<!--这里传入参数不需要使用?使用key=value-->
<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>
自定义地区解析组件
Spring源码中关于国际化的类有:
Locale
:代表地区,每一个Locale对象都代表了一个特定的地理、政治和文化地区LocaleResolver
:地区解析器
分析:
消息语言转换的类:AcceptHeaderLocaleResolver,实现接口 LocaleResolver
LocaleResolver:
public interface LocaleResolver {Locale resolveLocale(HttpServletRequest request);void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);}
在WebMvcAutoConfiguration 类中寻找 LocaleResolver:
@Override@Bean@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)public LocaleResolver localeResolver() {//如果用户配置了,则使用用户配置好的if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {return new FixedLocaleResolver(this.webProperties.getLocale());}//用户没有配置,则使用默认的AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();localeResolver.setDefaultLocale(this.webProperties.getLocale());return localeResolver;}
经过寻找后发现只要完成了 接口实现类 LocalResolver,就能完成 地区语言转换。该方法就是获取LocaleResolver
地区对象解析器:
- 如果用户配置了则使用用户配置的地区解析器;
- 如果用户没有配置,则使用默认的地区解析器
在消息语言转换的类 AcceptHeaderLocaleResolver中:
public class AcceptHeaderLocaleResolver implements LocaleResolver {private final List<Locale> supportedLocales = new ArrayList<>(4);@Nullableprivate Locale defaultLocale;......@Overridepublic Locale resolveLocale(HttpServletRequest request) {Locale defaultLocale = getDefaultLocale();if (defaultLocale != null && request.getHeader("Accept-Language") == null) {return defaultLocale;}Locale requestLocale = request.getLocale();List<Locale> supportedLocales = getSupportedLocales();if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {return requestLocale;}Locale supportedLocale = findSupportedLocale(request, supportedLocales);if (supportedLocale != null) {return supportedLocale;}return (defaultLocale != null ? defaultLocale : requestLocale);}......@Overridepublic void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {throw new UnsupportedOperationException("Cannot change HTTP accept header - use a different locale resolution strategy");}}
我们自定义一个地区解析器,继承实现LocaleResolver 接口,使我们的自定义国际化资源生效。去实现,前端界面 Index.html中点击链接传递参数,使得按钮生效,完成中英文切换。
- 定义自己的地区配置解析器LocalResolver
- 前端页面的请求链接参数
index.html:
<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>
自定义地区配置解析器:MyLocaleResolver,完成实现接口LocalResolver。
- 按照源码中那样编写, 我们要获取 index.html中的语言链接请求,然后进行对应的地区语言转换:
package com.al.config;import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;// 地区解析器:获取链接上携带的参数信息
public class MyLocaleResolver implements LocaleResolver {// 解析请求@Overridepublic Locale resolveLocale(HttpServletRequest request) {// 获取请求中的国家化参数String language = request.getParameter("l");// 默认的地区Locale locale = Locale.getDefault();//如果请求的链接参数不为空,携带了国际化参数if (!StringUtils.isEmpty(language)) {String[] split = language.split("_");//zh_CN(语言_地区)locale = new Locale(split[0], split[1]);}return locale;}@Overridepublic void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {}
}
我们的国际化 message 写完后, 为了使地区配置解析器的信息生效。该组件需要放入到 bean中。
在配置类 MvcConofig 中添加bean:
package com.al.config;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration public class MyMvcConfig implements WebMvcConfigurer {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/").setViewName("index");registry.addViewController("/index.html").setViewName("index");}// 将自定义的国际化配置放入 Spring容器中,使组件生效@Beanpublic LocaleResolver localeResolver(){return new MyLocaleResolver();} }
重启项目进行访问测试。
点击中文按钮,url路径为:http://localhost:8080/index.html?l=zh_CN
点击英文按钮,url路径为:http://localhost:8080/index.html?l=en_US
4、登录功能的实现
前端页面 index.html
在登录页面,即首页去实现登录, 实现跳转到 dashboard.html 页面。
- 在 index.html页面, 使用 thymeleaf 语法,改写提交动作 submit 跳转路径到 /user/login
- 给定 username 名字和password密码时 添加
name
属性,便于传参
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><meta name="description" content=""><meta name="author" content=""><title>Signin Template for Bootstrap</title><!-- Bootstrap core CSS --><link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"><!-- Custom styles for this template --><link th:href="@{/css/signin.css}" rel="stylesheet">
</head><body class="text-center">
<!--<form class="form-signin" action="dashboard.html">-->
<form class="form-signin" th:action="@{/user/login}"><img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72"><h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1><label class="sr-only">Username</label><input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus=""><label class="sr-only">Password</label><input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required=""><p style="color: red" th:text="${msg}"></p><div class="checkbox mb-3"><label>
<!-- <input type="checkbox" value="remember-me" th:text="#{login.remember}"> Remember me--><input type="checkbox" value="remember-me" th:text="#{login.remember}"></label></div>
<!-- <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>--><button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.btn}]]</button><p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<!-- <a class="btn btn-sm">中文</a>-->
<!-- <a class="btn btn-sm">English</a>--><!--这里传入参数不需要使用?使用key=value--><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></form></body></html>
控制器Controller
我们创建一个控制器用于 响应这个请求 servlet,完成跳转。
LoginController 控制器:
package com.al.controller;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;@Controller
public class LoginController {@RequestMapping("/user/login")public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model){if (!StringUtils.isEmpty(username) && "123456".equals(password)){return "dashboard";}else {model.addAttribute("msg", "用户名或密码错误");return "index";}}
}
然后我们在index.html
首页中加一个标签用来显示controller返回的错误信息:
<p style="color: red" th:text="${msg}"></p>
测试结果:
在密码不对的情况下:http://localhost:8080/user/login?username=ahoh&password=123
url路径里面显示了用户的名字和密码,这暴漏了用户的信息。我们编写一个映射 去解决这个问题。
在自定义的配置类MyMvcConfig 中添加映射。在url=/main.html时,页面为 登录后的页面 dashboard.html
@Configuration public class MyMvcConfig implements WebMvcConfigurer {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/").setViewName("index");registry.addViewController("/index.html").setViewName("index");registry.addViewController("/main.html").setViewName("dashboard");}// 将自定义的国际化配置放入 Spring容器中,使组件生效@Beanpublic LocaleResolver localeResolver(){return new MyLocaleResolver();} }
对应的,我们需要在 登录控制器 LoginController中进行重定向:redirect:/main.html
@Controller public class LoginController {@RequestMapping("/user/login")public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model){if (!StringUtils.isEmpty(username) && "123456".equals(password)){return "redirect:/main.html";}else {model.addAttribute("msg", "用户名或密码错误");return "index";}} }
测试:输入正确的用户名和密码登陆成功后,浏览器不再泄露信息。
但此时的结果虽然可以在登录后的后台界面进去了,但是你直接在url路径输入 /main.html 也能进去, 这就需要 拦截器了。
登录拦截器
创建一个登录拦截器 LoginHandlerInterceptor。
拦截器去完成登录拦截的功能:【用户登录后,后台会得到用户的信息;如果没有登录,则不会有任何的用户信息】。
- 重写拦截器接口 HandlerInterceptor的方法。有用户的 session 即用户信息()才让其允许通过。
- 在登录时的控制器 LoginController 中去加入用户的 session
LoginController控制器:
- 添加登录用户的session 信息
@Controller
public class LoginController {@RequestMapping("/user/login")public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model, HttpSession session){if (!StringUtils.isEmpty(username) && "123456".equals(password)){session.setAttribute("LoginUser", username);//return "dashboard";return "redirect:/main.html"; // 重定向}else {model.addAttribute("msg", "用户名或密码错误");return "index";}}
}
登录拦截器 LoginHandlerInterceptor:
编写一个自定义的登录拦截器 LoginHandlerInterceptor,继承HandlerInterceptor接口
- 去获取用户信息 session:如果没有,让其跳转到首页 index.html,且提示消息 msg;
- 如果不为空,放行
package com.al.config;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class LoginHandlerInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 用户如果登录成功了,应该获取到用户的 session 信息Object session = request.getSession().getAttribute("LoginUser");if (session == null) {request.setAttribute("msg", "权限不够,请先登录");request.getRequestDispatcher("/index.html").forward(request, response);return false;} else {return true;}}
}
注册拦截器 LoginHandlerInterceptor 到spring 容器中,Srping Boot配置中添加自定义配置资源
定义完拦截器后,我们需要进行在 bean中进行 注册配置:
在MyMvcConfig配置类中,重写关于拦截器的方法,添加自定义的拦截器
屏蔽:要拦截的以及不要拦截的 路径 资源,
需要放行的:.excludePathPatterns(“/index.html”, “/”, “/user/login”, “/css/", "/js/”, “/img/**”)。才能将页面完全导入进来,页面加载出来
package com.al.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class MyMvcConfig implements WebMvcConfigurer {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/").setViewName("index");registry.addViewController("/index.html").setViewName("index");registry.addViewController("/main.html").setViewName("dashboard");}// 将自定义的国际化配置放入 Spring容器中,使组件生效@Beanpublic LocaleResolver localeResolver(){return new MyLocaleResolver();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/index.html", "/", "/user/login", "/css/**", "/js/**", "/img/**");}
}
如果不放行静态资源,页面无法完全导入:
@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/index.html", "/", "/user/login");}
修改后测试:此时直接访问http://localhost:8080/main.html 显示权限不够:
重新写入username 和password,进入到dashboard页面。
如果再重新直接访问 http://localhost:8080/main.html ,也可以直接进入。因为此时的 session 里面存入了用户的信息,拦截器会放行通过。
5、员工信息:增删改查
展示员工列表
实现Customers视图跳转
目标,功能:点击dashboard.html页面中的Customers 按钮跳转到 list.html列表,展示所有的员工信息。
我们想要 点击员工管理,就去跳转到 员工管理的界面:
- 那么,我们把这个对应的员工管理点击 动作 对应的超链接改为 url地址就可以了:
- 员工管理展示信息列表的 url 请求响应后,对应的 controller 去控制这个请求 servlet: EmployeeController
- 可能随着项目扩展,员工变多, 所以将 list页面移动到 创建的 emp文件中,或许以后还有其它页面。
dashboard.html页面的代码修改:
修改后:
<li class="nav-item"><a class="nav-link" th:href="@{/emps}"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>员工管理</a></li>
同样修改list.html
对应该的代码为上述代码:
我们在
templates
目录下新建一个包emp
,用来放所有关于员工信息的页面,我们将list.html
页面移入该包中
在这里插入图片描述
员工管理控制器:EmployeeController:
- 对于请求的controller,处理 /emps 这一url请求,在controller 包下,创建EmployeeController 类 去对 /emps 请求进行响应
package com.al.controller;import com.al.dao.EmployeeDao;
import com.al.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.Collection;// 员工管理信息 controller
@Controller
public class EmployeeController {@Autowiredprivate EmployeeDao employeeDao;@RequestMapping("/emps")public String list(Model model){Collection<Employee> employees = employeeDao.getAll();model.addAttribute("emps",employees);return "emps/list"; // 返回到 list界面}
}
测试:点击Customers
,成功跳转到/emps
改进的地方:
- 点击员工管理,我们想要实现高亮的效果
- 需要对侧边栏进行修改, 对应着员工管理这些选项。类似的,我们也将顶部导航栏 进行插入, 实现代码的复用:
提取页面公共部分:侧边栏、顶部栏
对于这些公共的地方, 我们创建一个 commons文件夹去进行存放, commons.html 实现代码复用。
commons.html
commons.html 公共部分的复用代码:顶部导航栏与侧边导航栏:
- 顶部导航栏,利用th:fragment提取出来,命名为topbar【th:fragment=“topbar”】
- 侧边栏,利用th:fragment提取出来,命名为sidebar【th:fragment=“siderbar”】
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"><!--顶部导航栏,利用th:fragment提取出来,命名为topbar-->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar"><a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Companyname</a><input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search"><ul class="navbar-nav px-3"><li class="nav-item text-nowrap"><a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a></li></ul>
</nav><!--侧边栏,利用th:fragment提取出来,命名为sidebar-->
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="siderbar"><div class="sidebar-sticky"><ul class="nav flex-column"><li class="nav-item"><a class="nav-link active" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"stroke-linejoin="round" class="feather feather-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>Dashboard <span class="sr-only">(current)</span></a></li><li class="nav-item"><a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"stroke-linejoin="round" class="feather feather-file"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>Orders</a></li><li class="nav-item"><a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"stroke-linejoin="round" class="feather feather-shopping-cart"><circle cx="9" cy="21" r="1"></circle><circle cx="20" cy="21" r="1"></circle><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path></svg>Products</a></li><li class="nav-item"><a class="nav-link" th:href="@{/emps}"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"stroke-linejoin="round" class="feather feather-users"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>员工管理</a></li><li class="nav-item"><a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"stroke-linejoin="round" class="feather feather-bar-chart-2"><line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line></svg>Reports</a></li><li class="nav-item"><a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"stroke-linejoin="round" class="feather feather-layers"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>Integrations</a></li></ul><h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted"><span>Saved reports</span><a class="d-flex align-items-center text-muted"href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg></a></h6><ul class="nav flex-column mb-2"><li class="nav-item"><a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"stroke-linejoin="round" class="feather feather-file-text"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>Current month</a></li><li class="nav-item"><a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"stroke-linejoin="round" class="feather feather-file-text"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>Last quarter</a></li><li class="nav-item"><a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"stroke-linejoin="round" class="feather feather-file-text"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>Social engagement</a></li><li class="nav-item"><a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"stroke-linejoin="round" class="feather feather-file-text"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>Year-end sale</a></li></ul></div>
</nav>
</html>
list.html 和 dashboard.html
删除掉 list.html 和 dashboard.html 中的侧边栏和顶部导航栏的代码:
dashboard.html中删除后剩下的:
<!DOCTYPE html> <!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ --> <html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><meta name="description" content=""><meta name="author" content=""><title>Dashboard Template for Bootstrap</title><!-- Bootstrap core CSS --><link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"><!-- Custom styles for this template --><link th:href="@{/css/dashboard.css}" rel="stylesheet"><style type="text/css">/* Chart.js */@-webkit-keyframes chartjs-render-animation {from {opacity: 0.99}to {opacity: 1}}@keyframes chartjs-render-animation {from {opacity: 0.99}to {opacity: 1}}.chartjs-render-monitor {-webkit-animation: chartjs-render-animation 0.001s;animation: chartjs-render-animation 0.001s;}</style></head><body><div class="container-fluid"><div class="row"><main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"><div class="chartjs-size-monitor" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;"><div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;"><div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0"></div></div><div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;"><div style="position:absolute;width:200%;height:200%;left:0; top:0"></div></div></div><div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom"><h1 class="h2">Dashboard</h1><div class="btn-toolbar mb-2 mb-md-0"><div class="btn-group mr-2"><button class="btn btn-sm btn-outline-secondary">Share</button><button class="btn btn-sm btn-outline-secondary">Export</button></div><button class="btn btn-sm btn-outline-secondary dropdown-toggle"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>This week</button></div></div><canvas class="my-4 chartjs-render-monitor" id="myChart" width="1076" height="454" style="display: block; width: 1076px; height: 454px;"></canvas></main></div></div><!-- Bootstrap core JavaScript================================================== --><!-- Placed at the end of the document so the pages load faster --><script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" ></script><script type="text/javascript" src="asserts/js/popper.min.js" ></script><script type="text/javascript" src="asserts/js/bootstrap.min.js" ></script><!-- Icons --><script type="text/javascript" src="asserts/js/feather.min.js" ></script><script>feather.replace()</script><!-- Graphs --><script type="text/javascript" src="asserts/js/Chart.min.js" ></script><script>var ctx = document.getElementById("myChart");var myChart = new Chart(ctx, {type: 'line',data: {labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],datasets: [{data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],lineTension: 0,backgroundColor: 'transparent',borderColor: '#007bff',borderWidth: 4,pointBackgroundColor: '#007bff'}]},options: {scales: {yAxes: [{ticks: {beginAtZero: false}}]},legend: {display: false,}}});</script></body></html>
list.html 中删除后剩下的:
<!DOCTYPE html> <!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ --> <html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><meta name="description" content=""><meta name="author" content=""><title>Dashboard Template for Bootstrap</title><!-- Bootstrap core CSS --><link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"><!-- Custom styles for this template --><link th:href="@{/css/dashboard.css}" rel="stylesheet"><style type="text/css">/* Chart.js */@-webkit-keyframes chartjs-render-animation {from {opacity: 0.99}to {opacity: 1}}@keyframes chartjs-render-animation {from {opacity: 0.99}to {opacity: 1}}.chartjs-render-monitor {-webkit-animation: chartjs-render-animation 0.001s;animation: chartjs-render-animation 0.001s;}</style></head><body><div class="container-fluid"><div class="row"><main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"><h2>Section title</h2><div class="table-responsive"><table class="table table-striped table-sm"><thead><tr><th>#</th><th>Header</th><th>Header</th><th>Header</th><th>Header</th></tr></thead><tbody><tr><td>1,001</td><td>Lorem</td><td>ipsum</td><td>dolor</td><td>sit</td></tr><tr><td>1,002</td><td>amet</td><td>consectetur</td><td>adipiscing</td><td>elit</td></tr><tr><td>1,003</td><td>Integer</td><td>nec</td><td>odio</td><td>Praesent</td></tr><tr><td>1,003</td><td>libero</td><td>Sed</td><td>cursus</td><td>ante</td></tr><tr><td>1,004</td><td>dapibus</td><td>diam</td><td>Sed</td><td>nisi</td></tr><tr><td>1,005</td><td>Nulla</td><td>quis</td><td>sem</td><td>at</td></tr><tr><td>1,006</td><td>nibh</td><td>elementum</td><td>imperdiet</td><td>Duis</td></tr><tr><td>1,007</td><td>sagittis</td><td>ipsum</td><td>Praesent</td><td>mauris</td></tr><tr><td>1,008</td><td>Fusce</td><td>nec</td><td>tellus</td><td>sed</td></tr><tr><td>1,009</td><td>augue</td><td>semper</td><td>porta</td><td>Mauris</td></tr><tr><td>1,010</td><td>massa</td><td>Vestibulum</td><td>lacinia</td><td>arcu</td></tr><tr><td>1,011</td><td>eget</td><td>nulla</td><td>Class</td><td>aptent</td></tr><tr><td>1,012</td><td>taciti</td><td>sociosqu</td><td>ad</td><td>litora</td></tr><tr><td>1,013</td><td>torquent</td><td>per</td><td>conubia</td><td>nostra</td></tr><tr><td>1,014</td><td>per</td><td>inceptos</td><td>himenaeos</td><td>Curabitur</td></tr><tr><td>1,015</td><td>sodales</td><td>ligula</td><td>in</td><td>libero</td></tr></tbody></table></div></main></div></div><!-- Bootstrap core JavaScript================================================== --><!-- Placed at the end of the document so the pages load faster --><script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"></script><script type="text/javascript" src="asserts/js/popper.min.js"></script><script type="text/javascript" src="asserts/js/bootstrap.min.js"></script><!-- Icons --><script type="text/javascript" src="asserts/js/feather.min.js"></script><script>feather.replace()</script><!-- Graphs --><script type="text/javascript" src="asserts/js/Chart.min.js"></script><script>var ctx = document.getElementById("myChart");var myChart = new Chart(ctx, {type: 'line',data: {labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],datasets: [{data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],lineTension: 0,backgroundColor: 'transparent',borderColor: '#007bff',borderWidth: 4,pointBackgroundColor: '#007bff'}]},options: {scales: {yAxes: [{ticks: {beginAtZero: false}}]},legend: {display: false,}}});</script></body></html>
测试,此时访问dashboard.html 或者 list.html(/emps)页面的结果:http://localhost:8080/emps
没有了侧边栏和顶部导航栏
插入侧边栏和导航栏:
- 插入: 并注意使用 thymeleaf 语法 ~ 波浪号
<!--顶部导航栏-->
<div th:replace="~{commons/commons::topbar}"></div><!--侧边栏-->
<div th:replace="~{commons/commons::siderbar}"></div>
测试:登陆后,成功导入侧边栏和导航栏。
点击显示高亮
那么对于 高亮而言:就是 active 这里的动作:
commons页面:
- 在公共页面
commons.html
相应标签部分利用thymeleaf接收参数active
,利用三元运算符判断决定是否高亮
<li class="nav-item"><a th:class="${active=='dashboard.html'?'nav-link active':'nav-link'}" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"stroke-linejoin="round" class="feather feather-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>Dashboard <span class="sr-only">(current)</span></a></li>
...<li class="nav-item"><a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"stroke-linejoin="round" class="feather feather-users"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>员工管理</a></li>
dashboard.html页面:
<!--侧边栏--><div th:replace="~{commons/commons::siderbar(active='dashboard.html')}"></div>
同样在list.html
的侧边栏标签传递参数active
为list.html
:
<!--侧边栏--><div th:replace="~{commons/commons::siderbar(active='list.html')}"></div>
测试:
此时点击员工管理,员工管理高亮,成功。
显示员工信息
修改 list.html 页面,展示员工的信息。
员工管理控制器 EmployeeController:
- url请求 http://localhost:8080/emps 访问所有的员工信息。
- 返回结果 集合 employees 封装在 emps中,利用 Model 将数据传递到前端
package com.al.controller;import com.al.dao.EmployeeDao;
import com.al.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.Collection;// 员工管理信息 controller
@Controller
public class EmployeeController {@Autowiredprivate EmployeeDao employeeDao;@RequestMapping("/emps")public String list(Model model){Collection<Employee> employees = employeeDao.getAll();model.addAttribute("emps",employees);return "emps/list"; // 返回到 list界面}
}
修改表格的展示信息:获取参数的信息表示:【从emps中获取,遍历表示】
<td th:text="${emp.getId()}"></td>
<td>[[${emp.getLastName()}]]</td>
上述修改后,重新启动程序,可以展示员工的信息列表。不过此时的
- gender 性别显示的是 0 与 1,而不是男/女。需要进行修改。使用三元运算符解决。
- 且对于操作一栏,添加 编辑和删除的 按钮功能。
- 日期一栏的格式,修改为 yyyy-MM-dd HH:mm:ss 的形式
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"><h2>Section title</h2><div class="table-responsive"><table class="table table-striped table-sm"><thead><tr><th>id</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.getId()}"></td>--><td th:text="${emp.getId()}"></td><td>[[${emp.getLastName()}]]</td><td th:text="${emp.getEmail()}"></td><td th:text="${emp.getGender()==0?'女':'男'}"></td><td th:text="${emp.getDepartment().getDepartmentName()}"></td><td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td><td><a class="btn btn-sm btn-primary">编辑</a><a class="btn btn-sm btn-danger">删除</a></td></tr></tbody></table></div></main>
小结:
在登录过程中:
- 用户名和密码的判断,决定是否成功登录。
- 只有用户成功登录后,有了用户 session,才能进行访问特定界面。 拦截器【拦截请求,还需放行资源】
在展示员工列表的时候:
提取页面公共部分,代码复用。
<!--定义: 顶部导航栏,利用th:fragment提取出来,命名为topbar--> <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar"><!-- 插入: 顶部导航栏--> <div th:replace="~{commons/commons::topbar}"></div>
员工信息在列表中循环/遍历获取 进行展示。
增加员工实现
员工管理的增加:
- 在员工管理界面 添加 员工增加的请求url,按钮选项。
- add 添加员工这一请求servlet 对应的控制器 controller。跳转到 add.html页面,进行添加
- 创建 添加页面:add.html
添加员工选项
在员工信息展示列表 list.html 界面中添加 员工增加(add)选项,和对应的url请求路径:
<h2><a class="btn btn-sm btn-success" th:href="@{/add}">添加员工</a></h2>
效果:
EmployeeController 控制器
编写 add 添加员工这一请求的 controller。
- 通过Get方式提交,返回add.html页面(在该页面完成员工信息添加)
@GetMapping("/add")public String toAddPage(Model model) {//查出所有的部门信息,添加到departments中,用于前端接收Collection<Department> departments = departmentDao.getDepartments();model.addAttribute("departments", departments);return "emps/add";//返回到添加员工页面}
在这里,toAddPage函数传递 部门信息给前端,便于在添加员工信息时 去选择所在部门信息。
- model.addAttribute(“departments”, departments);
增加员工信息界面:add.html
此时的department 信息 传递给前端, 在前端显示出来所有部门的信息。 add.thml 页面的修改:
add.html :
- 复制
list.html
中的内容,把员工表格信息展示的部分修改为:
<!--顶部导航栏-->
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid"><div class="row"><!--侧边栏--><div th:replace="~{commons/commons::siderbar(active='list.html')}"></div><main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"><div class="form-group"><label>LastName</label><input type="text" name="lastName" class="form-control" placeholder="lastname:zsr"></div><div class="form-group"><label>Email</label><input type="email" name="email" class="form-control" placeholder="email:xxxxx@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><!--注意这里的name是department.id,因为传入的参数为id--><select class="form-control" name="department.id"><option th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option></select></div><div class="form-group"><label>Birth</label><!--springboot默认的日期格式为yy/MM/dd--><input type="text" name="date" class="form-control" placeholder="birth:yyyy/MM/dd"></div><button type="submit" class="btn btn-primary">添加</button></main></div>
</div>
点击添加员工,成功跳转到add.html
页面:
add.html页面完成员工信息添加功能
在前面增加员工实现的功能,已经完成了:
- 在list.html这一员工信息展示列表 添加了 增加员工 选项,以及响应 增加员工请求(/add) 的控制器 controller
- 员工控制器EmployeeController 中添加 toAddPage 方法,前往增加员工界面 add.html,且传递 部门信息
- 增加员工界面 add.html 的代码编写,展示员工的 lastName、email、department、gender的信息,且添加 ‘name’ 属性,便于将相应的属性值由前端传递给后端,添加数据到底层。这里是添加到 模拟数据库表中的员工数据信息的 Map<Integer, Employee> employees 中。
那么增加员工实现 功能完成后,跳转页面到 list.html中,展示所有的员工信息。
- 增加选项动作提交时的url响应。这里也同样使用 /add 请求路径,不过请求方式改为 Post。【使用Resful风格】
- 编写对应的 controller,并在该控制器中 获取此时的员工map,传递给 list.html界面,展示添加员工后的 员工信息列表。
add.html:
<!--顶部导航栏-->
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid"><div class="row"><!--侧边栏--><div th:replace="~{commons/commons::siderbar(active='list.html')}"></div><main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"><form th:action="@{/add}" method="post"><div class="form-group"><label>LastName</label><input type="text" name="lastName" class="form-control" placeholder="lastname:zsr"></div><div class="form-group"><label>Email</label><input type="email" name="email" class="form-control" placeholder="email:xxxxx@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><!--注意这里的name是department.id,因为传入的参数为id--><select class="form-control" name="department.id"><option th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option></select></div><div class="form-group"><label>Birth</label><!--springboot默认的日期格式为yy/MM/dd--><input type="text" name="date" class="form-control" placeholder="birth:yyyy/MM/dd"></div><button type="submit" class="btn btn-primary">添加</button></form></main></div>
</div>
编写对应的controller,同样在EmployeeController
中添加一个方法addEmp
用来处理点击添加按钮
的操作
package com.al.controller;import com.al.dao.DepartmentDao;
import com.al.dao.EmployeeDao;
import com.al.pojo.Department;
import com.al.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.Collection;// 员工管理信息 controller
@Controller
public class EmployeeController {@Autowiredprivate EmployeeDao employeeDao;@Autowiredprivate DepartmentDao departmentDao;@RequestMapping("/emps")public String list(Model model){Collection<Employee> employees = employeeDao.getAll();model.addAttribute("emps",employees);return "emps/list"; // 返回到 list界面}@GetMapping("/add")public String toAddPage(Model model) {//查出所有的部门信息,添加到departments中,用于前端接收Collection<Department> departments = departmentDao.getDepartments();model.addAttribute("departments", departments);return "emps/add";//返回到添加员工页面}@PostMapping("/add")public String addEmp(Employee employee){System.out.println("save=>" +employee);employeeDao.addEmployee(employee); //添加一个员工return "redirect:/emps";//重定向到/emps,刷新列表,返回到list页面}
}
重启程序,进行测试,选择添加员工进入到 add.html界面:【注意:此时的日期是yyyy/MM/dd 的形式】
点击添加选项,url请求 /add,resful风格的 Post请求提交方式,响应的控制器是 addEmp,完成员工信息添加,跳转到list.html界面。展示的所有员工信息为:这时候,发现错误,无法跳转到 list.html界面。错误的原因:
在dao层的 EmployeeDao 发生了错误:
- 增加员工时的:employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId())); 不正确
// 增加一个员工public void addEmployee(Employee employee){if (employee.getId() == null){employee.setId(initId++);}employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));employees.put(employee.getId(), employee);System.out.println(employee);}
经过测试发现:
- employee.getDepartment().getId() 没有问题,获取的是 department部门的id 102
- departmentDao.getDepartmentById(102) 这里出错了
关于 departmentDao 中的方法无法调用。???错误还没解决掉
但是如果有问题的话,在EmployeeController 控制器 中的 toAddPage里面的
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments", departments);
这两行代码应该生效才对。 还是不对劲。…
修改员工信息
界面中,关于员工信息列表展示的时候,操作栏添加了 编辑 和 删除 的选项。同样的,对于编辑选项:【和添加员工信息的步骤类似】
- 对于编辑选项,添加请求路径,url: /emp/{id} ,请求的时候携带员工id,便于查询对应的员工信息,进行展示 和修改
- /emp/{id} 这一请求 servlet 对应的控制器 controller,跳转到更新界面 update.html
- 在 update.html 点击 修改 按钮的一瞬间,我们需要返回到list页面,更新员工信息,因此我们需要添加 href 属性,实现点击按钮时发起一个请求 /edit
list.html界面中的编辑选项
点击编辑选项,
- 增加 url 请求路径。
- 且前端需要传递给后端的参数:携带员工id
<a class="btn btn-sm btn-primary" th:href="@{/emp/{id}(id=${emp.getId()})}">编辑</a>
EmployeeController 控制器
响应 url请求,获取前端传递过来的员工 id 信息,跳转到 update.html 页面,展示员工 目前的信息:
@GetMapping("/emp/{id}")public String toUpdatePage(@PathVariable("id")Integer id, Model model){//查出原来的数据Employee employee = employeeDao.getEmployeeById(id);model.addAttribute("emp", employee);//查出所有部门的信息Collection<Department> departments = departmentDao.getDepartments();model.addAttribute("departments", departments);return "emps/update";}
updat.html 页面
这里的信息展示 和 add.html页面类似,复制进行修改:
不能每次修改一次员工信息,就让id+1, 需要id隐藏域标签
<input type="hidden" name="id" th:value="${emp.getId()}">
显示员工的原有部门信息,以及所有的部门信息
<select class="form-control" name="department.id"><option th:selected="${emp.getDepartment().getId() == department.getId()}" th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option></select>
规定一下显示的日期格式
<input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}" type="text" name="birth" class="form-control" placeholder="birth:yyyy/MM/dd">
update.html:
<!--顶部导航栏-->
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid"><div class="row"><!--侧边栏--><div th:replace="~{commons/commons::siderbar(active='list.html')}"></div><main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"><form th:action="@{/updateEmp}" method="post"><input type="hidden" name="id" th:value="${emp.getId()}"><div class="form-group"><label>LastName</label><input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="lastname:zsr"></div><div class="form-group"><label>Email</label><input th:value="${emp.getEmail()}" type="email" name="email" class="form-control" placeholder="email:xxxxx@qq.com"></div><div class="form-group"><label>Gender</label><br/><div class="form-check form-check-inline"><input th:checked="${emp.getGender()==1}" 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 th:checked="${emp.getGender()==0}"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><!--注意这里的name是department.id,因为传入的参数为id--><select class="form-control" name="department.id"><option th:selected="${emp.getDepartment().getId() == department.getId()}" th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option></select></div><div class="form-group"><label>Birth</label><!--springboot默认的日期格式为yy/MM/dd-->
<!-- <input th:value="${emp.getBirth()}" type="text" name="birth" class="form-control" placeholder="birth:yyyy/MM/dd"> -->
<!-- <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm')}" type="text" name="birth" class="form-control" placeholder="birth:yyyy/MM/dd">--><input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}" type="text" name="birth" class="form-control" placeholder="birth:yyyy/MM/dd"></div><button type="submit" class="btn btn-primary">修改</button></form></main></div>
</div>
update.html页面完成员工信息修改功能
员工信息修改后, 需要重新跳转 或者重定向到 员工列表的页面。
在 update.html 这个修改员工信息页面完成后, 提交动作需要执行 发送 更新页面完成这个请求, 然后才能重定向到员工列表页面:
update.html:
<form th:action="@{/updateEmp}" method="post">
对应的控制器 Controller:
- 处理点击
修改
按钮的请求。同样在EmployeeController
中添加一个方法EditEmp
用来处理edit
页面点击添加的操作
@PostMapping("/updateEmp")public String updataEmp(Employee employee){employeeDao.addEmployee(employee);//添加一个员工return "redirect:/emps";//添加完成重定向到/emps,刷新列表}
进行测试,进入添加页面,填写相关信息
点击修改,返回list.html页面:【我取消掉关于employeeDao中增加员工中的departmentDao 才有如下结果】
同样的错误:?????????????????????????????
- employee.getDepartment().getId() 没有问题,获取的是 department部门的id 102
- departmentDao.getDepartmentById(102) 这里出错了
关于 departmentDao 中的方法无法调用。???错误还没解决掉
但是如果有问题的话,在EmployeeController 控制器 中的 toAddPage里面的
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments", departments);
这两行代码应该生效才对。 还是不对劲。…
删除员工信息
对于操作栏的删除,响应删除请求,url请求对应的controller,直接返回list.html界面。
删除选项增加超链接:【和编辑选项类似,超链接属性 href,且传递参数应携带员工id】
<a class="btn btn-sm btn-danger" th:href="@{/delemp/{id}(id=${emp.getId()})}">删除</a>
EmployeeController 控制器:
响应url请求 /delemp/{id},移除员工信息,返回重定向到 list.html 页面。
@GetMapping("/delemp/{id}")public String deleteEmp(@PathVariable("id")Integer id, Model model){employeeDao.deleteEmployeeByID(id);return "redirect:/emps";//添加完成重定向到/emps,刷新列表}
测试,成功删除。
6、404页面定制和注销操作
404页面定制
只需要在templates
目录下新建一个error
包,然后将404.html
放入其中,报错SpringBoot就会自动找到这个页面。
注销操作
员工管理系统进行注销,即移除掉 session。
注销的功能在 commons.html文件中:
- 顶部导航栏处中的标签添加
href
属性,实现点击发起请求/user/logout
<li class="nav-item text-nowrap"><a class="nav-link" th:href="@{/user/logout}">Sign out</a>
</li>
控制器controller,响应 /user/logout 请求,在LoginController
中编写对应的方法,清除session,并重定向到首页。
@RequestMapping("/user/logout")public String logout(HttpSession session) {session.invalidate();return "redirect:/index.html";}
测试,重启程序,成功注销。
如何写一个网站呢?
前端
- 模板:利用别人写好的,进行改写成自己想要的
- 框架:去进行拼接。Bootstrap,layui,semantic-ui。 完成 栅格系统,导航栏,侧边栏,表单等
设计数据库【数据库表的设计、持久性、保护、备份】
后端操作数据库。【事务、设计模式,提高开发效率的】
后端和前端的数据交互。数据接口对接:json,对象 all in one
SpringBoot: 整合Mybatis-员工管理系统
对于这个员工管理系统,整合mybatis去完成对数据库操作。
创建数据库表
数据库排序规则:
- utf8_general_ci 不区分大小写
- utf8_general_cs 区分大小写
建立的数据库表:
department 部门表:
SHOW CREATE DATABASE school;
create table `department`(
`id` int(4) not null auto_increment comment'部门id',
`departmentName` VARCHAR(30) NOT NULL DEFAULT '机密部门' COMMENT '姓名',
primary key(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8insert into `department`(`id`,`departmentName`)
values(101,'户部'),(102,'兵部'),(103,'吏部'),(104,'刑部'),(105,'礼部'),(106,'工部')
外键
概念:如果公共关键字在一个关系中是主关键字,那么这个公共关键字被称为另一个关系的外键。由此可见,外键表示了两个关系之间的相关联系。以另一个关系的外键作主关键字的表被称为主表,具有此外键的表被称为主表的从表。
在实际操作中,将一个表的值放入第二个表来表示关联,所使用的值是第一个表的主键值(在必要时可包括复合主键值)。此时,第二个表中保存这些值的属性称为外键(foreign key)。
作用:保持数据一致性,完整性,主要目的是控制存储在外键表中的数据,约束。使两张表形成关联,外键只能引用外表中的列的值或使用空值。
创建表的时候指定外键的约束:
方式一:在创建表的时候,增加约束(麻烦,复杂)CREATE TABLE IF NOT EXISTS `school`.`grade` ( `gradeid` INT(10) NOT NULL auto_increment COMMENT '年级编号', `gradename` VARCHAR(10) not null COMMENT '年级名字', PRIMARY KEY(`gradeid`) )ENGINE=INNODB DEFAULT CHARSET=utf8CREATE TABLE if not EXISTS`student` ( `id` int(4) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '学号', `name` varchar(20) NOT NULL DEFAULT '匿名' COMMENT '姓名', `pwd` varchar(20) NOT NULL DEFAULT '123456' COMMENT '密码', `sex` varchar(2) NOT NULL DEFAULT '男' COMMENT '性别', `gradeid` INT(10) NOT NULL auto_increment COMMENT '年级编号', `birthday` datetime DEFAULT NULL COMMENT '出生日期', `address` varchar(60) DEFAULT NULL COMMENT '住址', `email` varchar(50) DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (`id`),KEY `FK_gradeid` (`gradeid`),CONSTRAINT `FK_gradeid` FOREIGN KEY (`gradeid`) REFERENCES `grade` (`gradeid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
创建表之后再去修改:
方式二:创建表之后再创建外键关系alter table `student` add constraint `FK_gradeid` foreign key(`gradeid`) references `grade` (`gradeid`); -- alter table `表名` -- add constraint `约束名` foreign key(作为外键的列) references 哪个表(哪个字段);
删除有外键关系的表的时候,必须要先删除引用别人的表(从表),再删除自己的表()。即先删除子表,再删除主表。
以上的操作都是物理外键,数据库级别的外键,,我们不建议使用,避免数据库过多造成困扰。
最佳实践
- 数据库就是单纯的表,只用来存数据,只有行和列
- 我们想使用多张表的数据,想使用外键,程序去实现
员工表:employee
create table `employee`( `id` INT(4) NOT NULL AUTO_INCREMENT COMMENT'员工id', `lastName` VARCHAR(30) NOT NULL COMMENT '姓名', `email` VARCHAR(30) NOT NULL DEFAULT '*.com' COMMENT '邮箱', `gender` INT(4) NOT NULL DEFAULT '0' COMMENT '性别', `department` INT(4) NOT NULL COMMENT '部门id', `birth`DATETIME DEFAULT NULL COMMENT '生日', primary key(`id`), key `FK_id`(`department`), constraint `FK_id` foreign key(`department`) references `department`(`id`) )ENGINE=INNODB DEFAULT CHARSET=utf8insert into `employee`(`id`,`lastName`,`email`,`gender`,`department`,`birth`) values(1001,'AAA','1356207897@qq.com',0,101,'2020-04-05'),(1002,'BBB','1356207897@qq.com',0,102,'2020-04-06'), (1003,'CCC','1356207897@qq.com',1,103,'2020-05-05'),(1004,'DDD','1356207897@qq.com',1,104,'2020-10-05'), (1005,'EEE','1356207897@qq.com',1,103,'2020-11-05'),(1006,'FFF','1356207897@qq.com',0,106,'2021-04-05')
数据库表成功创建后的结果:
创建SpringBoot项目
新建一个SpringBoot项目:springboot-employee-management-system-mybatis。
选择的springBoot配置有 web、JDBC、MySQL的:
注意:我们需要导入 thymeleaf 模板引擎的maven依赖:
<!--thymeleaf. 我们都是基于 3.x 开发的--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>
项目建立之后,将原来的静态资源文件,导入到现在的这个项目中。
建立数据库连接
对数据库配置文件进行连接的时候,导入 JDBC、mysql、Mybatis的maven依赖。
- mybatis 的依赖
- 静态资源导出的依赖
pom.xml :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.7</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.AL</groupId><artifactId>springboot-employee-management-system-mybatis</artifactId><version>0.0.1-SNAPSHOT</version><name>springboot-employee-management-system-mybatis</name><description>Employee Management System Mybatis for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--thymeleaf. 我们都是基于 3.x 开发的--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!--mybatis的maven依赖--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency><!-- Druid数据源--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.21</version></dependency><!-- log4j日志--><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins><!--静态资源导出问题--><resources><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource><resource><directory>src/main/resources</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource></resources></build></project>
数据库配置资源文件:
我使用 application.properties 去进行数据信息配置 。
application.properties 资源配置文件:
# 自定义ul请求目录
#server.servlet.context-path=/al#自定义配置:国际化资源文件
spring.messages.basename=i18n.login# 数据库配置信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
进行测试,建立数据库连接成功。
测试类文件:
package com.AL;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;@SpringBootTest
class SpringbootEmployeeManagementSystemMybatisApplicationTests {// DI 注入数据源。从spring IOC 容器中获取数据配置信息@AutowiredDataSource dataSource;@Testvoid contextLoads() throws SQLException {// 查看默认的数据源: com.zaxxer.hikari.HikariDataSourceSystem.out.println(dataSource.getClass());// 获得数据库连接:Connection connection = dataSource.getConnection()System.out.println(connection); // HikariProxyConnection@522082506 wrapping com.mysql.cj.jdbc.ConnectionImpl@57f847af//DruidDataSource druidDataSource = (DruidDataSource) dataSource;//System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());//System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());connection.close(); // 关闭连接}}
显示出了默认的数据源:
pojo层
创建实体类:Department 和 Employee 类。
Department:
package com.AL.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;// 部门表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {private Integer id;private String departmentName;
}
Employee:
package com.AL.pojo;import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;// 员工表
@Data
@NoArgsConstructor
public class Employee {private Integer id;private String lastName;private String email;private Integer gender; // 0:女 1:男。 用数字表示性别男女private Department department;private Date birth;public Employee(Integer id, String lastName, String email, Integer gender, Department department, Date birth) {this.id = id;this.lastName = lastName;this.email = email;this.gender = gender;this.department = department;// 默认的创建日期//this.birth = birth;this.birth = new Date();}
}
dao层
在这里,为了和上面的区分开来,创建Mapper目录以及对应Mapper接口、Mapper接口映射文件,完成对数据库的操作。
DepartmentMapper.java
package com.AL.mapper;import com.AL.pojo.Department;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;import java.util.List;//@Mapper : 表示本类是一个 MyBatis 的 Mapper
@Mapper
@Repository
public interface DepartmentMapper {// 获取所有部门信息List<Department> getDepartments();// 通过id获得部门Department getDepartment(Integer id);}
DepartmentMapper.xml:关于DepartmentMapper 接口的映射文件,实现类。 完成对数据库的操作。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace= 绑定一个对应的Dao/Mapper接口
<mapper namespace="org.mybatis.example.BlogMapper"><select id="selectBlog" resultType="Blog">select * from Blog where id = #{id}
-->
<mapper namespace="com.AL.mapper.DepartmentMapper"><select id="getDepartments" resultType="com.al.pojo.Department">select * from department;</select><select id="getDepartment" resultType="Department" parameterType="int">select * from department where id = #{id};</select></mapper>
整合Myabtis
在配置文件中,整合myabtis。绑定关于dao层的接口和接口实现类 映射文件:
application.properties:
# 数据库配置信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8# 整合mybatis
mybatis.type-aliases-package=com.AL.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
Controller层
关于DepartmentController 的代码:
package com.AL.controller;import com.AL.mapper.DepartmentMapper;
import com.AL.pojo.Department;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
public class DepartmentController {@AutowiredDepartmentMapper departmentMapper;// 查询全部部门@GetMapping("/getDepartments")public List<Department> getDepartments(){return departmentMapper.getDepartments();}// 查询全部部门@GetMapping("/getDepartment/{id}")public Department getDepartment(@PathVariable("id") Integer id){return departmentMapper.getDepartment(id);}
}
启动主程序进行测试,发现错误:
org.thymeleaf.exceptions.TemplateInputException: Error resolving template [index], template might not exist or might not be accessible by any of the configured Template Resolvers
检查后,错误的原因是 thymeleaf 的依赖重复了,写了两遍。
在新建项目之后,去进行关于 controller 层的测试,发现了错误:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'departmentController': Unsatisfied dependency expressed through field 'departmentMapper';
错误的意思是:创建名为“departmentController”的bean时出错:通过字段“departmentMapper”表示的依赖关系不满足;
检查发现,是我的DepartmentMapper.xml中的代码写错了,修改后:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace= 绑定一个对应的Dao/Mapper接口
<mapper namespace="org.mybatis.example.BlogMapper"><select id="selectBlog" resultType="Blog">select * from Blog where id = #{id}
-->
<mapper namespace="com.AL.mapper.DepartmentMapper"><select id="getDepartments" resultType="com.AL.pojo.Department">select * from department;</select><select id="getDepartment" resultType="com.AL.pojo.Department" parameterType="int">select * from department where id = #{id};</select></mapper>
启动主程序,进行测试:http://localhost:8080/getDepartments
对于根据 id 访问部门信息:http://localhost:8080/getDepartment/102
员工 Employee
那么对于员工信息Employee 的CRUD操作,同样和部门信息类似,去建立dao层、controller进行测试,完成相关的操作。
Mapper层
EmployeeMapper:
package com.AL.mapper;import com.AL.pojo.Employee;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;import java.util.List;//@Mapper : 表示本类是一个 MyBatis 的 Mapper
@Mapper
@Repository
public interface EmployeeMapper {// 获取所有员工信息List<Employee> getEmployees();// 新增一个员工int addEmployee(Employee employee);// 通过id获得员工信息Employee getEmployeeById(Integer id);// 通过id删除员工int deleteEmployeeByID(Integer id);}
EmployeeMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace= 绑定一个对应的Dao/Mapper接口
<mapper namespace="org.mybatis.example.BlogMapper"><select id="selectBlog" resultType="Blog">select * from Blog where id = #{id}
-->
<mapper namespace="com.AL.mapper.EmployeeMapper"><select id="getEmployees" resultType="com.AL.pojo.Employee">select * from employee</select><delete id="deleteEmployeeByID" parameterType="int">delete from employee where id=#{id}</delete></mapper>
Controller层
package com.AL.controller;import com.AL.mapper.EmployeeMapper;
import com.AL.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
public class EmployeeController {@AutowiredEmployeeMapper employeeMapper;// 获取所有员工信息@GetMapping("/getEmployees")public List<Employee> getEmployees(){return employeeMapper.getEmployees();}// 通过id删除员工@GetMapping("/delete/{id}")public int delete(@PathVariable("id") Integer id){return employeeMapper.deleteEmployeeByID(id);}}
测试,访问 http://localhost:8080/getEmployees ,获取的所有 员工信息列表为:
[{"id":1001,"lastName":"AAA","email":"1356207897@qq.com","gender":0,"department":101,"birth":"2020-04-05T00:00:00.000+00:00"},{"id":1002,"lastName":"BBB","email":"1356207897@qq.com","gender":0,"department":102,"birth":"2020-04-06T00:00:00.000+00:00"},{"id":1003,"lastName":"CCC","email":"1356207897@qq.com","gender":1,"department":103,"birth":"2020-05-05T00:00:00.000+00:00"},{"id":1004,"lastName":"DDD","email":"1356207897@qq.com","gender":1,"department":104,"birth":"2020-10-05T00:00:00.000+00:00"},{"id":1005,"lastName":"EEE","email":"1356207897@qq.com","gender":1,"department":103,"birth":"2020-11-05T00:00:00.000+00:00"},{"id":1006,"lastName":"FFF","email":"1356207897@qq.com","gender":0,"department":106,"birth":"2021-04-05T00:00:00.000+00:00"}]
可以发现,能够成功的显示员工所有的信息。不过 我们会想要的是关于部门的具体信息,而不仅仅是部门id。对于这种的解决方法,可以采用resultMap,映射的方法去解决。
resultMap
在这里修改的时候发现,为了更好的 方便的展示信息,需要在实体类 Employee 中绑定部门Department 类的信息。如下所示:
Employee:
package com.AL.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;// 员工表
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {private Integer id;private String lastName;private String email;private Integer gender; // 0:女 1:男。 用数字表示性别男女private Integer department; //原有的属性,对应数据库的 department id, 但对应的想要的是一个表private Date birth;private Department eDepartment; // 冗余设计}
按照查询嵌套处理
多对一: 关联。 一对多:集合。
如何让员工表格和 部门表格去建立连接, 关联 起来。 需要用到map 以及 association。
思路:
1.查询所有的员工信息
2.根据查询出来的员工信息的 department(即部门id),寻找对应的部门信息
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace= 绑定一个对应的Dao/Mapper接口
<mapper namespace="org.mybatis.example.BlogMapper"><select id="selectBlog" resultType="Blog">select * from Blog where id = #{id}
-->
<mapper namespace="com.AL.mapper.EmployeeMapper"><!--查询员工信息: 单纯的 department id-->
<!-- <select id="getEmployees" resultType="com.AL.pojo.Employee">-->
<!-- select * from employee-->
<!-- </select>-->
<resultMap id="EmployeeDepartment" type="Employee"><result property="id" column="id"/><result property="lastName" column="LastName"/><result property="email" column="email"/><result property="gender" column="gender"/><result property="birth" column="birth"/><!--复杂的属性:我们需要单独处理 对象:association 集合:collection--><!--复杂的属性:我们需要单独处理
对象 关联:association 集合:collection
association : 关联,多对一
column : 数据库对应的列名
property : 对应属性名
javaType : 多对一 字段对应的Java类型
select : 关联一个语句
--><association property="eDepartment" column="department" javaType="Department" select="getDepartment"/>
</resultMap><select id="getDepartment" resultType="Department">select * from department where id = #{id}</select><select id="getEmployees" resultMap="EmployeeDepartment">select * from employee</select><delete id="deleteEmployeeByID" parameterType="int">delete from employee where id=#{id}</delete></mapper>
这时,重新启动程序进行测试,成功显示 department的所有信息。
分析:
association property=“eDepartment” column=“department” javaType=“Department” select=“getDepartment” 在这里面的 property 对应实体类中的属性,column对应数据库表中的字段名,javaType表示多对一(该字段对应的Java类型)。从这里去绑定部门 Department 的信息。
按照结果嵌套处理
将查询到的结果进行封装到对象的属性中,重新进行展示。
<!-- <!–结果封装,将查询出来的列封装到对象属性中–>--><resultMap id="EmployeeDepartment2" type="Employee"><result property="id" column="eid"/><result property="lastName" column="lastName"/><result property="email" column="email"/><result property="gender" column="gender"/><result property="birth" column="birth"/><association property="eDepartment" javaType="Department"><result property="id" column="did"/><result property="departmentName" column="dname"/></association></resultMap><select id="getEmployees" resultMap="EmployeeDepartment2">select e.id as eid,lastName,email,gender,birth,d.id as did,d.departmentName as dnamefrom department d,employee ewhere d.id = e.department</select>
启动程序,进行测试,可以成功的查询到所有员工的信息以及对应的部门id和部门名称。
[{"id":1001,"lastName":"AAA","email":"1356207897@qq.com","gender":0,"birth":"2020-04-05T00:00:00.000+00:00","edepartment":{"id":101,"departmentName":"户部"}},{"id":1002,"lastName":"BBB","email":"1356207897@qq.com","gender":0,"birth":"2020-04-06T00:00:00.000+00:00","edepartment":{"id":102,"departmentName":"兵部"}},{"id":1003,"lastName":"CCC","email":"1356207897@qq.com","gender":1,"birth":"2020-05-05T00:00:00.000+00:00","edepartment":{"id":103,"departmentName":"吏部"}},{"id":1004,"lastName":"DDD","email":"1356207897@qq.com","gender":1,"birth":"2020-10-05T00:00:00.000+00:00","edepartment":{"id":104,"departmentName":"刑部"}},{"id":1005,"lastName":"EEE","email":"1356207897@qq.com","gender":1,"birth":"2020-11-05T00:00:00.000+00:00","edepartment":{"id":103,"departmentName":"吏部"}},{"id":1006,"lastName":"FFF","email":"1356207897@qq.com","gender":0,"birth":"2021-04-05T00:00:00.000+00:00","edepartment":{"id":106,"departmentName":"工部"}}]
分析:【自己的理解】
在这里的
<association property="eDepartment" javaType="Department"><result property="id" column="did"/><result property="departmentName" column="dname"/></association>
里面的 eDepartment 是Employee实体类的一个属性,这个属性对应的其实是多个字段(一个类Department)。 那么在这种 resulMap的map映射里面,将这些查询到的结果 字段值 重新封装到一个对应的表里面,对应着 想要的对象属性,然后去进行展示。
Employee的完善
对于剩下的CRUD操作代码的完善。
EmployeeMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace= 绑定一个对应的Dao/Mapper接口
<mapper namespace="org.mybatis.example.BlogMapper"><select id="selectBlog" resultType="Blog">select * from Blog where id = #{id}
-->
<mapper namespace="com.AL.mapper.EmployeeMapper"><!--查询员工信息: 单纯的 department id-->
<!-- <select id="getEmployees" resultType="com.AL.pojo.Employee">-->
<!-- select * from employee-->
<!-- </select>--><!--按照查询嵌套处理-->
<!--<resultMap id="EmployeeDepartment" type="Employee">-->
<!-- <result property="id" column="id"/>-->
<!-- <result property="lastName" column="LastName"/>-->
<!-- <result property="email" column="email"/>-->
<!-- <result property="gender" column="gender"/>-->
<!-- <result property="birth" column="birth"/>-->
<!-- <!–复杂的属性:我们需要单独处理 对象:association 集合:collection–>-->
<!-- <!–复杂的属性:我们需要单独处理-->
<!--对象 关联:association 集合:collection-->
<!--association : 关联,多对一-->
<!--column : 数据库对应的列名-->
<!--property : 对应属性名-->
<!--javaType : 多对一 字段对应的Java类型-->
<!--select : 关联一个语句-->
<!--–>-->
<!-- <association property="eDepartment" column="department" javaType="Department" select="getDepartment"/>-->
<!--</resultMap>-->
<!-- <select id="getDepartment" resultType="Department">-->
<!-- select * from department where id = #{id}-->
<!-- </select>-->
<!-- <select id="getEmployees" resultMap="EmployeeDepartment">-->
<!-- select * from employee-->
<!-- </select>--><!-- <!–结果封装,将查询出来的列封装到对象属性中–>--><resultMap id="EmployeeDepartment2" type="Employee"><result property="id" column="eid"/><result property="lastName" column="lastName"/><result property="email" column="email"/><result property="gender" column="gender"/><result property="birth" column="birth"/><association property="eDepartment" javaType="Department"><result property="id" column="did"/><result property="departmentName" column="dname"/></association></resultMap><select id="getEmployees" resultMap="EmployeeDepartment2">select e.id as eid,lastName,email,gender,birth,d.id as did,d.departmentName as dnamefrom department d,employee ewhere d.id = e.department</select><insert id="addEmployee" parameterType="Employee">insert into employee (lastName,email,gender,department,birth)values (#{lastName},#{email},#{gender},#{department},#{birth});</insert><!--select * from employee where id = #{id}--><select id="getEmployeeById" parameterType="int" resultMap="EmployeeDepartment2">select e.id as eid,lastName,email,gender,birth,d.id as did,d.departmentName as dnamefrom department d,employee ewhere d.id = e.department and e.id = #{id}</select><delete id="deleteEmployeeByID" parameterType="int">delete from employee where id=#{id}</delete></mapper>
EmployeeController:
package com.AL.controller;import com.AL.mapper.DepartmentMapper;
import com.AL.mapper.EmployeeMapper;
import com.AL.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;import java.util.Date;
import java.util.List;@RestController
public class EmployeeController {@AutowiredEmployeeMapper employeeMapper;@AutowiredDepartmentMapper departmentMapper;// 获取所有员工信息@GetMapping("/getEmployees")public List<Employee> getEmployees(){return employeeMapper.getEmployees();}@GetMapping("/add")public int save(){Employee employee = new Employee();employee.setLastName("alzn");employee.setEmail("hhhhhh@qq.com");employee.setGender(1);employee.setEDepartment(departmentMapper.getDepartment(101));employee.setBirth(new Date());return employeeMapper.addEmployee(employee);}// 通过id获得员工信息@GetMapping("/get/{id}")public Employee getById(@PathVariable("id") Integer id){return employeeMapper.getEmployeeById(id);}// 通过id删除员工@GetMapping("/delete/{id}")public int delete(@PathVariable("id") Integer id){return employeeMapper.deleteEmployeeByID(id);}}
重新启动程序,进行测试,成功展示员工的信息以及对应的部门信息。
整合前端界面
将前面的项目里面的资源配置文件复制到此时项目里面:
关于url请求拦截、静态资源放行的配置文件和用户登录验证的控制器、首页显示控制器:
登录首页实现
首页显示控制器:
- 所有的访问界面需要从 index.html 界面进行登录。直接跳转到该界面
@Controller
public class IndexController {@RequestMapping({"/", "/index.html"}) //这是一个集合public String index(){return "index";}
}
去测试,看能否访问到该页面信息,去让用户进行登录。
错误信息:
Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)org.thymeleaf.exceptions.TemplateInputException: Error resolving template [index], template might not exist or might not be accessible by any of the configured Template Resolvers
可以发现编译后的 targets里面没有我们的静态资源配置文件 templates:
这属于静态资源导出时的问题,我们查找后,对pom.xml 配置资源文件进行修改:
- 加入**/*.html 配置和其它的
- src/main/resources **/.yml **/.properties **/.xml **/.html false
<!--静态资源导出问题--><resources><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource><resource><directory>src/main/resources</directory><includes><include>**/*.properties</include><include>**/*.yaml</include><include>**/*.xml</include><include>**/*.html</include><include>**/*.js</include><include>**/*.css</include></includes><filtering>false</filtering></resource></resources>
我们使静态资源能够导出,此时再进行测试的结果:
登录功能的实现
用户登录控制器和拦截器配置:
- LoginController:
package com.Al.controller;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;import javax.servlet.http.HttpSession;@Controller
//@RestController
public class LoginController {@RequestMapping("/user/login")public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model, HttpSession session){if (!StringUtils.isEmpty(username) && "123456".equals(password)){session.setAttribute("LoginUser", username);//return "dashboard";return "redirect:/main.html"; // 重定向}else {model.addAttribute("msg", "用户名或密码错误");return "index";}}@RequestMapping("/user/logout")public String logout(HttpSession session) {session.invalidate();return "redirect:/index.html";}
}
- 登录时的拦截器:LoginHandlerInterceptor
package com.AL.config;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class LoginHandlerInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 用户如果登录成功了,应该获取到用户的 session 信息Object session = request.getSession().getAttribute("LoginUser");if (session == null) {request.setAttribute("msg", "权限不够,请先登录");request.getRequestDispatcher("/index.html").forward(request, response);return false;} else {return true;}}
}
地域解析器配置:
package com.AL.config;import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;// 地区解析器:获取链接上携带的参数信息
public class MyLocaleResolver implements LocaleResolver {// 解析请求@Overridepublic Locale resolveLocale(HttpServletRequest request) {// 获取请求中的国家化参数String language = request.getParameter("l");// 默认的地区Locale locale = Locale.getDefault();//如果请求的链接参数不为空,携带了国际化参数if (!StringUtils.isEmpty(language)) {String[] split = language.split("_");//zh_CN(语言_地区)locale = new Locale(split[0], split[1]);}return locale;}@Overridepublic void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {}
}
MVC配置信息:
package com.AL.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class MyMvcConfig implements WebMvcConfigurer {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/").setViewName("index");registry.addViewController("/index.html").setViewName("index");registry.addViewController("/main.html").setViewName("dashboard");}// 将自定义的国际化配置放入 Spring容器中,使组件生效@Beanpublic LocaleResolver localeResolver(){return new MyLocaleResolver();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/index.html", "/", "/user/login", "/css/**", "/js/**", "/img/**");}
}
启动程序,进行测试,发现能够成功登录,且也可以进行拦截没有用户session的url请求。
控制器修改
在整合mybatis时测试的控制器,直接返回json形式的数据。只管展示。
在这里,为了和原先模拟数据库显示前端界面,需要对控制器中后端传递给前端数据 的代码进行修改。
查询:员工信息列表展示
在controller包下创建 EmployeeControllerDemo02 控制器。查询员工所有的信息,后端程序将结果封装在 Model 中的 emps 中,传递给前端。前端的emps 进行foreach 进行展示员工的信息。
EmployeeControllerDemo02:
package com.AL.controller;import com.AL.mapper.DepartmentMapper;
import com.AL.mapper.EmployeeMapper;
import com.AL.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.List;// 员工管理信息
@Controller
public class EmployeeControllerDemo02 {@AutowiredEmployeeMapper employeeMapper;@AutowiredDepartmentMapper departmentMapper;@RequestMapping("/emps")public String list(Model model){List<Employee> employeeList = employeeMapper.getEmployees();model.addAttribute("emps",employeeList);return "emps/list"; // 返回到 list界面}
}
前端界面显示 员工的信息。
- 需要注意的是,在这里,我们的实体类pojo中的 Employee 的属性为:
public class Employee {private Integer id;private String lastName;private String email;private Integer gender; // 0:女 1:男。 用数字表示性别男女private Integer department; //原有的属性,对应数据库的 department id, 但对应的想要的是一个表private Date birth;private Department eDepartment; // 冗余设计
}
- 对应的数据库表 employee 的字段为:
create table `employee`(
`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT'员工id',
`lastName` VARCHAR(30) NOT NULL COMMENT '姓名',
`email` VARCHAR(30) NOT NULL DEFAULT '*.com' COMMENT '邮箱',
`gender` INT(4) NOT NULL DEFAULT '0' COMMENT '性别',
`department` INT(4) NOT NULL COMMENT '部门id',
`birth`DATETIME DEFAULT NULL COMMENT '生日',
primary key(`id`),
key `FK_id`(`department`),
constraint `FK_id` foreign key(`department`) references `department`(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8
而前面的,我们编写的dao层中的 EmployeeMapper.xml中查询所有的员工信息,返回的结果为:
<resultMap id="EmployeeDepartment2" type="Employee"><result property="id" column="eid"/><result property="lastName" column="lastName"/><result property="email" column="email"/><result property="gender" column="gender"/><result property="birth" column="birth"/><association property="eDepartment" javaType="Department"><result property="id" column="did"/><result property="departmentName" column="dname"/></association>
</resultMap>
<select id="getEmployees" resultMap="EmployeeDepartment2">select e.id as eid,lastName,email,gender,birth,d.id as did,d.departmentName as dnamefrom department d,employee ewhere d.id = e.department
</select>
我们需要在前端中修改的部分就是关于 获取部门信息 重新进行展示的部分。如下所示:
list.html :
<tr th:each="emp:${emps}">
<!-- <td th:text="${emp.getId()}"></td>--><td th:text="${emp.getId()}"></td><td>[[${emp.getLastName()}]]</td><td th:text="${emp.getEmail()}"></td><td th:text="${emp.getGender()==0?'女':'男'}"></td>
<!-- <td th:text="${emp.getDepartment().getDepartmentName()}"></td>--><td th:text="${emp.getEDepartment().getDepartmentName()}"></td><td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td><td><a class="btn btn-sm btn-primary" th:href="@{/emp/{id}(id=${emp.getId()})}">编辑</a><a class="btn btn-sm btn-danger" th:href="@{/delemp/{id}(id=${emp.getId()})}">删除</a></td>
</tr>
测试,访问员工管理界面,查看是否成功显示所有的 员工信息。
增加员工
增加员工实现的步骤:
- 在list.html前端界面添加 员工增加的 选项及其超链接,响应url请求,跳转到toAddPage页面
- toAdd控制器,响应 /add 这一url请求。后端需要传递前端一个部门信息(部门id、name名称),注意add.html显示部门选项 option
- add.html 员工信息添加页面展示,尤其注意部门选项option这一部分的东西
- 员工的信息添加完成后,重定向到 list.html页面,显示所有的员工信息。
EmployeeControllerDemo02:控制器中的 toadd 和add 方法,去响应对应着请求url
@GetMapping("/add")public String toAddPage(Model model) {//查出所有的部门信息,添加到departments中,用于前端接收Collection<Department> departments = departmentMapper.getDepartments();model.addAttribute("departments", departments);return "emps/add";//返回到添加员工页面}@PostMapping("/add")public String addEmp(Employee employee){System.out.println("save=>" +employee);employeeMapper.addEmployee(employee); //添加一个员工return "redirect:/emps";//重定向到/emps,刷新列表,返回到list页面}
add.html:员工信息添加,重点注意的是 部门信息展示
- 注意: 我将这里的属性 name 值设置成了department,这样才和 employee中的属性对应:select class=“form-control” name=“department”
- option中显示的信息是 部门的名称,所以 th:text=“${department.getDepartmentName()}”
- 传递的是部门id,所以是 th:value=“${department.getId()}” 对应着name=“department” 传递给前端
<form th:action="@{/add}" method="post"><div class="form-group"><label>LastName</label><input type="text" name="lastName" class="form-control" placeholder="lastname:zsr"></div><div class="form-group"><label>Email</label><input type="email" name="email" class="form-control" placeholder="email:xxxxx@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><!--注意这里的name是department.id,因为传入的参数为id-->
<!-- <select class="form-control" name="department.id">--><!--注意: 我将这里的属性 name 值设置成了department,这样才和 employee中的属性对应--><select class="form-control" name="department"><option th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option></select></div><div class="form-group"><label>Birth</label><!--springboot默认的日期格式为yy/MM/dd--><input type="text" name="birth" class="form-control" placeholder="birth:yyyy/MM/dd"></div><button type="submit" class="btn btn-primary">添加</button></form>
测试,查看跳转到 添加员工界面:
点击添加选项,成功重定向到 list.html页面,展示所有员工信息:
解决了这里的之后,那么在前面的那里,员工添加 信息 出错的原因,是不是也是这个呢?
我需要对springboot-03-system 项目中的add.html 的前端传递部门信息进行修改。查看这个问题
修改员工
修改员工的信息:和增加员工信息的步骤类似。
EmployeeControllerDemo02:
@GetMapping("/emp/{id}")public String toUpdatePage(@PathVariable("id")Integer id, Model model){//查出原来的数据Employee employee = employeeMapper.getEmployeeById(id);model.addAttribute("emp", employee);//查出所有部门的信息Collection<Department> departments = departmentMapper.getDepartments();model.addAttribute("departments", departments);return "emps/update";}@PostMapping("/updateEmp")public String updataEmp(Employee employee){employeeMapper.addEmployee(employee);//添加一个员工return "redirect:/emps";//添加完成重定向到/emps,刷新列表}
update.html:
- 重要的同样是 部门信息展示的部分。我自己修改的部门信息
<div class="form-group"><label>department</label><!--注意这里的name是department.id,因为传入的参数为id-->
<!-- <select class="form-control" name="department.id">--><select class="form-control" name="department">
<!-- <option th:selected="${emp.getDepartment().getId() == department.getId()}" th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option>--><option th:selected="${emp.getEDepartment().getId() == department.getId()}" th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option></select>
</div>
进行测试,查看是否能够修改员工信息:
修改后重定向到 list.html 页面:
- 发现有两个该员工。检查后,发现自己在这里使用的方法里面是 addEmployee,所以导致增加了一个员工信息。
删除员工
直接删除员工信息,重定向到 list.html页面。
EmployeeControllerDemo02:
@GetMapping("/delemp/{id}")public String deleteEmp(@PathVariable("id")Integer id, Model model){employeeMapper.deleteEmployeeByID(id);return "redirect:/emps";//添加完成重定向到/emps,刷新列表}
测试,删除员工。成功完成。
Java-SpringBoot:员工管理系统相关推荐
- 狂神说JAVA SpringBoot员工管理系统
SpringBoot员工管理系统(没有数据库) 成品:https://download.csdn.net/download/weixin_43501359/14927226 SpringBoot员工管 ...
- SpringBoot员工管理系统(整合Mybatis+mysql)
SpringBoot员工管理系统(整合Mybatis+mysql) 前部分:https://blog.csdn.net/weixin_43501359/article/details/11271466 ...
- (附源码)springboot员工管理系统 毕业设计 021430
员工管理系统的设计与实现 摘 要 由于数据库和数据仓库技术的快速发展,企业员工管理系统建设越来越向模块化.智能化.自我服务和管理科学化的方向发展.员工管理系统对处理对象和服务对象,自身的系统结构,处理 ...
- java springboot房地产信息管理系统+实训报告+PPT+功能需求报告
博主介绍:✌在职Java研发工程师.专注于程序设计.源码分享.技术交流.专注于Java技术领域和毕业设计✌ 项目名称 java springboot房地产信息管理系统+实训报告+PPT+功能需求报告 ...
- [附源码]计算机毕业设计JAVA企业员工管理系统
[附源码]计算机毕业设计JAVA企业员工管理系统 项目运行 环境配置: Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(Inte ...
- [附源码]计算机毕业设计基于Java的员工管理系统Springboot程序
项目运行 环境配置: Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclis ...
- springBoot员工管理系统
员工管理系统 1.准备工作 静态资源:链接:https://pan.baidu.com/s/1qtUDuJNVupr872kVDO-veg 提取码:fabo gitee:https://gitee.c ...
- Java入门--员工管理系统
Java员工管理系统 需求 思路 定义员工类 主界面编写 查看员工的代码编写 新增员工代码的编写 判断工号是否被使用 修改员工代码的编写 删除员工的代码编写 需求 编写一个控制台版本的员工管理系统,员 ...
- Java实现员工管理系统
员工管理系统实现起来还是比较简单的,建一个员工类,里面包含员工的工号.姓名.年龄.性别和专业属性 public class Staff {private int id;private String n ...
- Java项目:员工管理系统(前后端分离+java+vue+Springboot+ssm+mysql+maven+redis)
源码获取:博客首页 "资源" 里下载! 一.项目简述 本系统功能包括:分为前端翻后端部分,包括用户,区分晋通用户以及誉里员用户,包括首页展示,部门管理,人事管理,员工管理三个模块等 ...
最新文章
- 计算机应用能力考试xp,全国专业技术人员计算机应用能力考试XP
- 电力网络故障的计算机算法,电力系统故障分析的计算机算法.pdf
- JVM 自带使用总结
- 最强内网穿透工具frp
- APS先进规划与生产排程系统--介绍
- Unity 角度与旋转(四元数与欧拉角)
- “对赌协议”—八大失败经典案例
- 【web】自定义协议Protocol URL
- (Mix) The task phx.new could not be found
- 计算机等级考试第一次报什么,计算机等级考试一年是考两次,3月和9月各一次 如果第一次没有考过第二次考要再报名缴费吗?...
- 迭代器Iterator的用法
- Lua Busted 单元测试简介(Windows 环境)
- 大数据分析界的“神兽”Apache Kylin初解
- aws mysql 费用_AWS都收了哪些费用?
- PhotoShop彩色图片打印机只有四中颜色操作步骤:
- Lil Test and WIP about FLIP
- mysql数据库应用与开发姜桂洪 课后答案_数据库应用与开发姜桂洪课后答案
- scanpy 单细胞分析包图文详解 01 | 深入理解 AnnData 数据结构
- java数据结构 农夫过河_数据结构笔记分享:18 农夫过河(图的算法运用)
- Java基础篇-笔记 p1-p75