Springboot入门(包括小项目)

入门之入门

简单介绍
Spring介绍

Spring是为了解决企业级应用开发的复杂性而创建的开发框架。

Spring为了简化开发,采取了4个关键策略:

1,基于POJO的轻量级和最小侵入性编程。

2,通过IOC(控制反转),依赖注入和面向接口实现松耦合。

3,通过AOP(面向切面)和惯例进行声明式编程。

4,通过切面和模板减少样式代码。

Springboot介绍

框架之框架,比Spring更简化,约定大于配置

其实仍然就是一个javaweb的开发框架。

微服务架构

微服务架构即打破从前all in one的架构方式,把每个功能元素独立出来,可以动态地再进行整合。

微服务架构是对功能元素进行复制,而非对整个应用进行复制。

Hello Springboot
快速上手

新建一个工程,简单地选一下:

在这里插入图片描述

先勾选web-Spring web,点finish:

在这里插入图片描述

以下几个多余的可以删掉:

在这里插入图片描述

等它加载完就可以运行了,如果报错Web server failed to start. Port 8080 was already in use.

就可以在resource下的application里改以下端口号,如:server.port=8081

下图表示运行成功,端口号为8081:

在这里插入图片描述

下面写个controller测试一下,建立好这个controller的包和类:

在这里插入图片描述

写一个hello/springboot页面的controller,返回文本“hello springboot”:

@Controller
@RequestMapping("/hello")
public class HelloController {
    @GetMapping("/springboot")
    @ResponseBody
    public String Hello(){
        return "hello springboot";
    }
}

进入http://localhost:8081/hello/springboot,看到网页上打出hello springboot,运行成功。

彩蛋功能

这里介绍一个彩蛋功能,

在resource目录下面新建一个banner.txt,可以修改程序启动时的图片,效果如下:

在这里插入图片描述

入门之理论

自动配置原理
pom文件

在pom.xml文件中,主要存放着核心依赖和启动器。

核心依赖:在父工程中,有版本仓库,所以有些依赖不需要我们指定版本。

启动器:即springboot的启动场景,如spring-boot-starter-web,会帮我们自动导入web环境的所有依赖。

springboot会将所有的功能场景都变成一个个的启动器,我们要使用什么功能,只需要找到对应的启动器。

主程序的注解

在Application中:

@SpringBootApplication
public class DemoPackApplication {

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

}

其中注解@SpringBootApplication标明这是一个springboot程序,点进去可以看到一些组合注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//上面几个是每一级都有的元注解,其他的注解要一级一级往下点:

@SpringBootConfiguration
//springboot的配置
	@Configuration
	//spring配置类
		@Component
		//说明这是一个spring的组件

@EnableAutoConfiguration
//自动配置
	@AutoConfigurationPackage
	//自动配置包
		@Import({Registrar.class})
		//包注册
	@Import({AutoConfigurationImportSelector.class})
	//自动配置导入选择,点进这个类,找到这个:
		List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
		//这个就是获取所有的配置

getCandidateConfigurations是一个核心方法,点进去:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
        ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

其中提到的META-INF/spring.factories是自动配置的核心文件,这东西在External Libraries里面:

在这里插入图片描述

可以点进其中的spring.factories看一下,里面就是所有的自动配置。

用一个流程图概括一下前述的结构:

在这里插入图片描述

结论:

1,springboot在启动的时候,从META-INF/spring.factories获取指定的值。

2,将这些自动配置的类导入容器,自动配置就会生效,帮我们进行配置。

3,以前我们需要手动配置的东西,springboot帮我们做了。

4,整合javaEE,解决方案和自动配置都在spring-boot-autoconfigure这个包下。

5,它会把所有需要的导入的组件以类名的方式返回,这些组件就会被添加进容器。

6,容器中也会存在非常多的xxxAutoConfigure文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件,并自动配置。

7,有了这些自动配置类,免去了我们手动编写配置文件的工作。

yaml语法

springboot自动生成的配置文件是application.properties,但官方推荐同yaml文件,

基本语法:

  • 大小写敏感
  • 使用缩进表示层级关系
  • 禁止使用tab缩进,只能使用空格键
  • 缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级。
  • 使用#表示注释
  • 字符串可以不用引号标注

我们可以把拓展名改成yml:

server:
  port: 8081

这是改变端口号的代码。

# 普通的key-value
name: Rosemary

# 对象
student:
  name: Rosemary
  age: 23

# 行内写法
student2: {name: Rosemary,age: 23}

# 数组
pet:
  -cat
  -dog
  -pig

pet2: [cat,dog,pig]

注意yaml语法对空格极其严格。

可以看到yaml可以写对象,不过一般不用这个写,到此打住。

实战准备

要解决的问题:

1,导入静态资源

2,首页定制

3,模板引擎Thymeleaf

4,装配扩展SpringMVC

5,增删改查

6,拦截器

7,国际化

静态资源导入

在resources下的static显然可以用来存放静态资源,

此外,还可以新建public和resources文件夹用来存放静态资源:

在这里插入图片描述

我们在这些地方都建一个1.js,分别写:

hello,public

hello,resources

hello,static

运行并打开网页,结果在网页上显示的是hello,resources,说明它的优先级最高,

将hello,resources删掉,重新运行,显示的是hello,static,说明它的优先级次之,

将hello,static删掉,重新运行,显示的是hello,public,说明它的优先级最低。

一般顾名思义,public放些公告资源,static放些图片之类的,resources放些大家上传的文件。

首页定制

首页也是放在静态资源里面,但网址是http://localhost:8081,后面不再杠什么了,

这个时候要用index.html作为文档名,就会自动映射到首页:

在这里插入图片描述

在resources/public下建立一个index.html,写上“首页”,运行并打开网页:

在这里插入图片描述

没有任何问题。通过对index.html进行定制,就可以定制首页了。

这里我顺便试了一下,将一个html5制作的魔塔直接扔到resources目录下,改文件夹名为public,

并覆盖掉原来的,就能在首页直接运行了:

在这里插入图片描述

Thymeleaf模板引擎
模板引擎入门

模板引擎是干什么的呢?在web开发中,前端会为我们提供HTML页面,其中的元素是静态的,

而模板引擎可以接管其中的任意元素,将动态的元素(如变量)插进HTML页面中,实现数据交互。

以往的模板引擎是JSP,springboot推荐用较新的Thymeleaf。

@Controller
@RequestMapping()
public class IndexController {
    @GetMapping("/test")
    @ResponseBody
    public String test(){
        return "test";
    }
}

首先添加一个indexController,@RequestMapping后面不加东西,@GetMapping后面是/test,

这样我们可以在http://localhost:8081/test中看到页面返回test,说明这个controller添加成功。

根据启动器一般的命名规则,引入Thymeleaf依赖:

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

再修改controller:

@Controller
public class TestController {
    @GetMapping("/test")
    public String test(){
        return "test";
    }
}

注意:注解要使用@Controller而非@RestController,也不能有@ResponseBody。

因为@RestController就等于@Controller加上@ResponseBody,

而@ResponseBody表示着这个返回体是直接交给浏览器处理而非映射到某个页面,

而现在我们要做的就是用模板引擎映射到test页面上。

test页面:

<!DOCTYPE html>
<html lang="en"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>TEST</h1>
</body>
</html>

运行测试,发现http://localhost:8081/test中打出来的不再是字符串“test”,而是标题1格式的“TEST”,

这说明“test”已经从字符串映射成我们的test.html页面了。

页面中加入了命名空间xmlns:th=“http://www.thymeleaf.org”,这让我们可以通过th来接管html的所有元素,

下面我们来试一下,给controller的方法加一个model参数,它可以用来携带后端传给前端的消息:

@Controller
public class TestController {
    @GetMapping("/test")
    public String test(Model model){
        model.addAttribute("msg","Hello,Thymeleaf");
        return "test";
    }
}

然后改一下test.html,用th元素来取这个msg值:

<!DOCTYPE html>
<html lang="en"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div th:text="${msg}"></div>
</body>
</html>

这里msg会爆红但是不影响,不知道为啥。由于h1标签本身没有text元素,所以改成了div标签。

访问http://localhost:8081/test,打出Hello,Thymeleaf,测试成功。

模板引擎语法

转译文本:

th元素可以接管所以html标签,而取值时用${}括起来,链接用@{}括起来。

上面用div标签打出了目标文本,但是想用h1怎么办呢?我们可以用th:utext来转译这个文本,

先小改一下testcontroller的一行代码:

model.addAttribute("msg","<h1>Hello,Thymeleaf</h1>");

就是给文本内容加了个h1标签,然后修改test.html:

<body>
<div th:text="${msg}"></div>
<div th:utext="${msg}"></div>
</body>

就是在原来的div标签下加一个标签,使用可以转译的utext元素,看看效果:

在这里插入图片描述

可以看到,不转译的text直接将h1标签当作文本输出了,转译的utxet则识别到了h1标签。

遍历:

下面介绍一个常用的遍历方法:

    public String test(Model model){
        model.addAttribute("msg","<h1>Hello,Thymeleaf</h1>");
        model.addAttribute("users", Arrays.asList("张三","李四","王五"));
        return "test";
    }

控制器加了一个用户列表。下面在test.html中用th:each来遍历它:

<body>
<div>[[${msg}]]</div>
<div th:utext=${msg}></div>
<div th:each="user:${users}">[[${user}]]</div>
</body>

结果:

在这里插入图片描述

测试成功。

其他语法跟java差不多,相当于直接在html里面写java。

装配扩展SpringMVC

看看SpringMVC支持的一些东西:

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.

(大多场景我们都无需自定义配置)

The auto-configuration adds the following features on top of Spring’s defaults:

● Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
○ 内容协商视图解析器和BeanName视图解析器

● Support for serving static resources, including support for WebJars (covered later in this document)).
○ 静态资源(包括webjars)

● Automatic registration of Converter, GenericConverter, and Formatter beans.
○ 自动注册 Converter,GenericConverter,Formatter

● Support for HttpMessageConverters (covered later in this document).
○ 支持 HttpMessageConverters (后来我们配合内容协商理解原理)

● Automatic registration of MessageCodesResolver (covered later in this document).
○ 自动注册 MessageCodesResolver (国际化用)

● Static index.html support.
○ 静态index.html 首页支持

● Custom Favicon support (covered later in this document).
○ 自定义 Favicon 图标

● Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
○ 自动使用 ConfigurableWebBindingInitializer (DataBinder负责将请求数据绑定到JavaBean上)

扩展MVC(保持原有配置并添加一些配置):

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.
不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则

首先新建一个mvcConfig类:

在这里插入图片描述

按照官方文档,这个类要添加@Configuration注解并实现WebMvcConfigurer 接口:

//扩展MVC
@Configuration
public class mvcConfig implements WebMvcConfigurer {
}

并且不能标注@EnableWebMvc注解,否则MVC会全面接管这个类。

@EnableWebMvc:从容器中获取所有的webmvcconfige

快捷键Alt+insert可以查看这个类能重写的方法:

在这里插入图片描述

例如我们要自定义视图解析器,只需要写一个类继承视图解析器,再通过@Bean注入容器:

    @Bean
    public ViewResolver MyViewResolver(){
        return new MyViewResolver();
    }

    public static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            return null;
        }
    }

下面我们来自定义一个视图跳转:

@Configuration
public class mvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/view").setViewName("test2");
    }
}

这里执行的是,当我们访问/view请求时,进入test2页面,我们再写一个test2.html放在templates下测试即可。

至此,我们已大致了解了实战前需要了解的一些原理。

员工管理系统

准备工作
pojo层

将controller都删掉,将mvcConfig弄干净点:

@Configuration
@EnableWebMvc
public class mvcConfig implements WebMvcConfigurer {

}

将页面资源导入templates,将静态资源导入static:

在这里插入图片描述

导入lombok的依赖:

		<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

数据库后面再讲,这里先伪造一下数据。建立pojo类,下面建立部门表和员工表:

在这里插入图片描述

部门表:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
    private Integer id;
    private String departmentName;
}

这三个注解是lombok自带的,可以自动引入getter、setter、有参和无参构造方法等:

在这里插入图片描述

员工表:

@Data
@NoArgsConstructor
public class Employee {
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender;
    private Department department;
    private Date birth;

    public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
        this.department = department;
        this.birth = new Date();
    }
}

没有有参构造的注解,而是自定义一个有参构造,让他内部生成一个Date给birth。

DAO层

建立部门和员工的DAO:

在这里插入图片描述

部门的DAO:

@Repository
public class DepartmentDAO {
    //模拟数据库中的数据
    private static Map<Integer, Department> departments = null;
    static {
        departments = new HashMap<Integer, Department>();
        departments.put(101,new Department(101,"教学部"));
        departments.put(102,new Department(102,"市场部"));
        departments.put(103,new Department(103,"技术部"));
    }

    //获得所有部门信息
    public Collection<Department> getDepartments(){
        return departments.values();
    }

    //根据id获得部门
    public Department getDepartmentById(Integer id){
        return departments.get(id);
    }
}

员工的DAO:

@Repository
public class EmployeeDAO {
    //模拟数据库中的数据
    private static Map<Integer, Employee> employees = null;
    @Autowired
    private DepartmentDAO departmentDAO;

    static {
        employees = new HashMap<Integer, Employee>();
        employees.put(1001,new Employee(1001,"Duo","12345@qq.com",1,new Department(101,"教学部")));
        employees.put(1002,new Employee(1002,"Rose","123456@qq.com",0,new Department(102,"市场部")));
        employees.put(1003,new Employee(1003,"Lily","1234567@qq.com",0,new Department(103,"技术部")));

    }

    //主键自增
    private static Integer initId = 1004;
    //增加一个新员工
    public void save(Employee employee){
        if (employee.getId() == null){
            employee.setId(initId++);
        }

        employee.setDepartment(departmentDAO.getDepartmentById(employee.getDepartment().getId()));
        employees.put(employee.getId(),employee);
    }

    //查询全部员工
    public Collection<Employee> getEmployees(){
        return employees.values();
    }

    //根据id查询员工
    public Employee getEmployeeById(Integer id){
        return employees.get(id);
    }

    //删除员工
    public void delEmpolyee(Integer id){
        employees.remove(id);
    }
}

完成这两个DAO,准备工作就结束了。

首页实现

我们的静态资源要被Thymeleaf接管的话,必须添加命名空间xmlns:th=“http://www.thymeleaf.org”,

然后在要使用css的地方,href改成th:href,链接用@{}括起来。要修改的三行代码如下:

		<!-- 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">
		<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">

然后我们映射到首页,可以直接在config里面写,注意把@EnableWebMvc去掉:

@Configuration
public class mvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("/index");
        registry.addViewController("/index.html").setViewName("/index");
    }
}

这样在访问/或者/index.html的时候都可以映射到我们的首页,我们来试一下:

在这里插入图片描述

成功来到了首页,并且图标和css样式都加载成功了。

如果不成功的话,可以清除浏览器缓存与Thymeleaf缓存:

spring.thymeleaf.cache = false
登录实现
简单登录

由于目前数据库是伪造的,我们让他点击登录时直接跳转页面就好了,

首先来到index页面,改一行代码:

		<form class="form-signin" th:action="@{/user/login}">

然后写个控制器实现这个请求:

@Controller
public class LoginController {

    @ResponseBody
    @RequestMapping("user/login")
    public String login(){
        return "OK";
    }
}

先测试一下,打出OK,再进行后面的操作。

然后实现登录功能,由于要提交表单,要给用户名和密码标签加个name:

			<label class="sr-only">Username</label>
			<input type="text" name="username" class="form-control" placeholder="Username" required="" autofocus="">
			<label class="sr-only">Password</label>
			<input type="password" name="password" class="form-control" placeholder="Password" required="">

然后在控制器接收用户名和密码,以及用于返回消息的model:

    @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";
        }
    }

现在的密码验证是用户名不为空,密码为“123456”就能登陆成功。

测试后发现msg消息没能成功返回,以及dashboard页面没能加载css样式,我们修改一下,

首先在h1标签下面加个p标签:

<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

这样就能在密码错误时返回消息了,

接下来打开dashboard页面,加入Thymeleaf的命名空间,并对所有链接和文本标签进行接管,

就可以加载css样式了(路径里的assert都删掉)。

最后,由于登录后的url有点难看,可以在mvcConfig里面加一个映射:

        registry.addViewController("user/main.html").setViewName("/dashboard");

然后将控制器return的dashboard改为重定向到main:

            return "redirect:main.html";

这样登录成功后的页面就是http://localhost:8081/user/main.html了,比较好看。

登录拦截器

拦截器需要写一个loginHandlerInterceptor,实现HandlerInterceptor中的preHandle方法,放在config下,

为此先在控制器添加一个session:

    @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 "redirect:main.html";
        }else {
            model.addAttribute("msg","用户名或密码错误");
            return "index";
        }
    }

然后实现preHandle方法:

public class loginHandlerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object loginUser = request.getSession().getAttribute("loginUser");
        if(loginUser == null){
            request.setAttribute("msg","没有权限,请先登录");
            request.getRequestDispatcher("index.html").forward(request,response);
            return false;
        }else {
           return true; 
        }
    }
}

拦截器写好之后,还要在mvcConfig中放进去:

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new loginHandlerInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/","/index","/user/login","/css/**","/js/**","/img/**");
    }

可以看到,要拦截哪些请求、要放行哪些请求都是可选的,十分方便。

不登录直接进入http://localhost:8081/user/main.html,就会被返回登录页面:

在这里插入图片描述

既然有了session,我们也可以在登录成功后将用户名打在页面上了,修改dashboard中的一行:

<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>

用双中括号取出session中loginUser的值即可。

页面与展示
抽取公共元素

回顾一下我们的主要任务:

1,首页配置:Thymeleaf接管所有静态页面

2,登录+拦截器

3,核心功能:增删改查

我们登录后的页面是dashboard,里面有一栏是Customers,我们用这一栏来展示员工列表:

<li class="nav-item">
	<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
		员工管理
	</a>
</li>

这里把图标给删了,改成中文。跳转的地址要改成自己写的controller,为此先写一个员工管理controller:

@Controller
public class EmployeeController {

    @Autowired
    EmployeeDAO employeeDAO;

    @RequestMapping("/emps")
    public String list(Model model){
        Collection<Employee> employees = employeeDAO.getEmployees();
        model.addAttribute("emps",employees);
        return "emps/list";
    }
}

注意在templates下面新建一个emps,用于存放list。

写好这个controller之后就可以改dashboard了:

<li class="nav-item">
	<a class="nav-link" th:href="@{/emps}">
		员工管理
	</a>
</li>

相应的,对list.html作出修改。

注意到我们页面的侧边栏和顶部菜单都是一样的,他们可以作为公共元素被抽取出来,

为此我们使用Thymeleaf自带的写法,将他们变成组件:

<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">

找到侧边栏标签,给他加个th:fragment=“sidebar”,意思是把这个标签定义为侧边栏组件,

在其他页面中,就可以使用这个名称来引用这整个标签,

例如在list.html中,删掉原来的侧边栏而引用这个:

<div th:insert="~{dashboard::sidebar}"></div>

测试一下,发现list页面中成功抽取了侧边栏,接下来对导航栏如法炮制:

<!--		dashboard.html-->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<!--		list.html-->
<div th:insert="~{dashboard::topbar}"></div>

再测试,发现抽取成功。但是这样写不好引用,

一般写法是把组件写在一个独立的页面commons里,在所有页面中要使用时都从commons中引用,

这样字在修改这些组件时就只需要在一个地方修改:

在这里插入图片描述

注意我们在引用时需要重新声明引用的路径:

<div th:replace="~{commons/commons::sidebar}"></div>
点击时高亮

我们在点击“员工管理”时,高亮的还是首页,这显然不合理,要将代码修改成点哪个哪个就高亮,

为此我们在不同的页面(dashboard和list)调用commons时,可以给他传一个参数:

<div th:replace="~{commons/commons::sidebar(active='main.html')}"></div>

意思是这个sidebar是被main页面调用了,在commons里面就可以接收这个参数:

<!--		首页-->
<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/user/main.html}">
<!--		员工管理-->
<a th:class="${active=='emps.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">

这样就可以实现点击时高亮的功能了,注意Thymeleaf中单引号与三元表达式的用法。

展示员工列表

list.html里面有个默认的写死的表单,按照自己的员工表修改表头:

<thead>
	<tr>
		<th>id</th>
		<th>lastName</th>
		<th>email</th>
		<th>gender</th>
		<th>department</th>
		<th>birth</th>
	</tr>
</thead>

将tbody标签里默认的数据删掉,这里的数据应当是遍历出来的,可以用th:each遍历:

<tbody>
	<tr th:each="emp:${emps}">
		<td th:text="${emp.getId}"></td>
		<td th:text="${emp.getLastName()}"></td>
		<td th:text="${emp.getEmail()}"></td>
		<td th:text="${emp.getGender()==1?'':''}"></td>
		<td th:text="${emp.getDepartment().getDepartmentName()}"></td>
		<td th:text="${emp.getBirth()}"></td>
	</tr>
</tbody>

这样就能成功遍历出我们的数据了(虽然是在DAO层编的):

在这里插入图片描述

然后可以加两个按钮用于编辑和删除:

<td>
	<button class="btn btn-sm btn-primary">编辑</button>
	<button class="btn btn-sm btn-danger">删除</button>
</td>

效果如图:

在这里插入图片描述

如果觉得日期比较丑,可以格式化一下:

<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>

编辑和删除的具体实现见下一章。

编辑与删除
增加员工实现

要实现增加员工,首先应该给个添加员工按钮,放在Section title的下面(或者干脆把Section title替换掉):

<h2><a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a></h2>

点击这个按钮,应当跳转到对应的添加员工页面,所以还需要写这个页面,为此先实现页面的跳转,

在EmployeeController那里加个GetMapping:

    @Autowired
    DepartmentDAO departmentDAO;

	@GetMapping("/emp")
    public String toAddPage(Model model){
        Collection<Department> departments = departmentDAO.getDepartments();
        model.addAttribute("departments",departments);
        return "emps/add";
    }

这里将后端所有的部门传给前端,用于添加员工时显示。接下来写添加员工页面,

将list页面复制一份,叫做add,然后进行一些修改。头部、公共部分是不用改的,只需要改main标签内部,

先将原来的删掉,然后写一份添加员工表单,样式可以再bootstrap上面找,这里直接放:

<form th:action="@{/emp}" method="post">
						<div class="form-group">
							<label>LastName</label>
							<input type="text" name="lastName" class="form-control" placeholder="">
						</div>
						<div class="form-group">
							<label>Email</label>
							<input type="email" name="email" class="form-control" placeholder="">
						</div>
						<div class="form-group">
							<label>Gender</label><br>
							<div class="form-check form-check-inline">
								<input class="form-check-input" type="radio" name="gender" value="1">
								<label class="form-check-label"></label>
							</div>
							<div class="form-check form-check-inline">
							<input class="form-check-input" type="radio" name="gender" value="0">
							<label class="form-check-label"></label>
						</div>
			</div>
			<div class="form-group">
				<label>department</label>
				<select class="form-control" name="department.id">
					<option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
				</select>
			</div>
			<div class="form-group">
				<label>Birth</label>
				<input type="date" name="birth" class="form-control" placeholder="">
				<button class="btn btn-primary">添加</button>
			</div>
			</form>

可以看到,表单提交地址依然是/emp,不过前面的添加员工按钮是get请求,这里是post,所以能识别。

下面要在控制器接收这个返回的表单:

    @PostMapping ("/emp")
    public String addEmp(Employee employee){
        employeeDAO.save(employee);
        return "redirect:/emps";
    }

由于我们的日期格式是yyyy-MM-dd,而系统默认是斜杠分隔的,还要加一条配置:

spring.mvc.format.date=yyyy-MM-dd

由于我们表单中生日的type=“date”,所以会自动转化成配置的格式:

在这里插入图片描述

可以看到,添加的员工id是自增的。

显示出来的日期带有时分秒,比较奇怪,可以在list.html里面删掉。

修改员工实现

要给编辑按钮一个跳转的连接,先要将button改成a标签,并加href:

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

控制器:

    @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";
    }

复制add页面,命名为update,修改其中的表单:

					<form th:action="@{/updateEmp}" method="post">
						<div class="form-group">
							<label>LastName</label>
							<input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="">
						</div>
						<div class="form-group">
							<label>Email</label>
							<input th:value="${emp.getEmail()}" type="email" name="email" class="form-control" placeholder="">
						</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>
				<select class="form-control" name="department.id">
					<option th:selected="${dept.getId()==emp.getDepartment().getId()}" th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
				</select>
			</div>
			<div class="form-group">
				<label>Birth</label>
				<input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}" type="date" name="birth" class="form-control" placeholder="">
				<button class="btn btn-primary">修改</button>
			</div>
			</form>

主要是每项添加了一个默认值,显示这个被选中的员工原来的值。要注意生日的格式化。

测试发现加载不了css:

在这里插入图片描述

发现是路径的css前面没有加斜杠,之前不出现这个错误可能是这里的路径多了一层/id,系统找不到css了,

添加css路径前的斜杠后样式加载正常:

		<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 */

添加接收修改的控制器:

    @PostMapping("/updateEmp")
    public String updateEmp(Employee employee){
        employeeDAO.save(employee);
        return "redirect:/emps";
    }

可以看到,逻辑跟添加员工是一样的。测试一下:

在这里插入图片描述

发现修改之后直接新增了一个多儿,而非替换掉原来的Duo,是底层员工id自增的问题,

只需要在update的表单里加一个隐藏域,传一下当前id,即可解决这个问题:

<input th:type="hidden" name="id" th:value="${emp.getId()}">

测试一下:

在这里插入图片描述

没有问题,此时如果新增员工,也是id=1004。

删除员工实现

list的链接:

<a class="btn btn-sm btn-danger" th:href="@{/del/}+${emp.getId}">删除</a>

控制器:

    @GetMapping("/del/{id}")
    public String delete(@PathVariable("id")Integer id){
        employeeDAO.delEmpolyee(id);
        return "redirect:/emps";
    }

这就写完了,测试成功。

小结:如何写一个网站

(前端)

模板:别人写好的,我们拿来改成自己需要的。

组件:自己手动组合拼接。

资源:bootstrap、semantic-ui

要掌握的:栅格系统、导航栏、侧边栏、表单

1,前端搞定:页面长什么样子

2,数据库设计

3,让前端能够自己运行,独立化工程

4,数据接口如何对接

5,前后端联调测试

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值