初入Java开发职场,接触好多开源框架,觉得有必要把知识累积下来。接下来就以一个小小的Demo做为SpringBoot学习的开篇吧,今后会围绕这个Demo把全部的知识穿插进去。
第一步:创建空工程
利用IDEA创建一个Spring Initializr工程,工程组、名、版本号等信息如下图:
在Dependencies部分仅仅选择Web模块就好,其他依赖的包后续会手动添加
接下来我们看看新创建出来的工程pom.xml文件中都导入了哪些依赖,此处仅仅展示了依赖的包,分别为:Spring web starter和test模块,如下:
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId>dependency><dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> <exclusions> <exclusion> <groupId>org.junit.vintagegroupId> <artifactId>junit-vintage-engineartifactId> exclusion> exclusions>dependency>
第二步:导入相关依赖包和配置
1、需要导入的包有两个,一个是连接mysql数据库相关的包,另一个是springboot的模版解析引擎thymeleaf。
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-data-jpaartifactId>dependency><dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> <scope>runtimescope>dependency><dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-thymeleafartifactId>dependency>
2、在application.properties增加连接mysql数据库相关配置项,配置项包括登入mysql的用户名、密码、数据库名(study库)、驱动,如下:
# mysql连接相关的配置spring.datasource.username=rootspring.datasource.password=123456spring.datasource.url=jdbc:mysql://localhost:3306/studyspring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
第三步:编写对数据库的CURD操作
在编写CURD操作前,线了解一下项目当前的整体目录结构:
接下来我们主要是编写dao包中的内容。
1、首先在dao包中新建一个entity的包,并且包下新建两个类EmployeeDO和DepartmentDO,我们先来看一下这两个DO的关键代码:
@Entity注解标明此类是一个数据库实体类;
@Table标明对应的数据库的表名;
@lombok是为了简化类的getter和setter方法等;
这个类中定义了员工的各种信息,其中@Id注解标明是数据库主键,@GeneratedValue标明主键的生成策略,同时@DateTimeFormat定义日期格式
@Entity(name = "EmployeeDO")@Table(name = "employee")@lombok.Datapublic class EmployeeDO { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long employeeId; private String name; private Integer gender; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date birth; private String email; private Long departmentId; private String describes; @Override public String toString() { return String.format( "employeeId:%d, name:%s, birth:%s, departmentId:%d, gender:%d, email:%s, describe:%s", employeeId, name, birth.toString(), departmentId, gender, email, describes); }}
@Entity(name = "DepartmentDO")@Table(name = "department")@lombok.Datapublic class DepartmentDO { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private Long departmentId; private String departmentName;}
这就定义好了代码实体与数据库表的对应关系。
2、接下来我们还需要在dao包中定义两个DAO类,来实现对数据库表的CURD操作,其中更新和删除操作需要标注Transactional注解,用于实现事务操作。
public interface EmployeeDAO extends JpaRepository<EmployeeDO, Long> { EmployeeDO findEmployeeDOByEmployeeId(Long employeeId); @Transactional void deleteByEmployeeId(Long employeeId); @Query("select max(e.employeeId) from EmployeeDO as e") Long getMaxEmployeeId(); @Transactional @Modifying @Query("update EmployeeDO as e set e.name = ?2,e.gender=?3,e.birth=?4,e.email=?5,e.departmentId=?6,e.describes=?7 where e.employeeId=?1") void updateEmployeeByEmployeeId(Long employeeId, String name, Integer gender, Date birth, String email, Long departmentId, String describes);}
public interface DepartmentDAO extends JpaRepository<DepartmentDO,Long> { DepartmentDO findDepartmentDOByDepartmentId(Long departmentId);}
接下来我们看一下dao层的目录结构
第四步:编写Service层和Contoller层
1、service层相关,首先定义一个前端展示员工信息用的EmployeeDTO对象,关于DO、DTO、VO等概念,详见文章(https://blog.csdn.net/catoop/article/details/90053285)
@lombok.Datapublic class EmployeeDTO { private Long employeeId; private String name; private Integer gender; private Date birth; private String email; private DepartmentDO departmentDO; private String describes;}
@Servicepublic class DepartmentService { @Autowired private DepartmentDAO departmentDAO; public ListfindAllDepartment(){ return departmentDAO.findAll(); }}
@Servicepublic class EmployeeService { @Autowired private EmployeeDAO employeeDAO; @Autowired private DepartmentDAO departmentDAO; public ListfindAllEmployees() { List employeeDTOList = new ArrayList<>(); List employeeDOList = employeeDAO.findAll(); if (CollectionUtils.isEmpty(employeeDOList)) { return new ArrayList<>(); } for (EmployeeDO employeeDO : employeeDOList) { EmployeeDTO employeeDTO = new EmployeeDTO(); BeanUtils.copyProperties(employeeDO, employeeDTO, "id", "departmentId"); DepartmentDO departmentDO = departmentDAO.findDepartmentDOByDepartmentId(employeeDO.getDepartmentId()); employeeDTO.setDepartmentDO(departmentDO); employeeDTOList.add(employeeDTO); } return employeeDTOList; } public EmployeeDTO findEmployeeById(Long employeeId) { EmployeeDTO employeeDTO = new EmployeeDTO(); EmployeeDO employeeDO = employeeDAO.findEmployeeDOByEmployeeId(employeeId); BeanUtils.copyProperties(employeeDO, employeeDTO, "id", "departmentId"); DepartmentDO departmentDO = departmentDAO.findDepartmentDOByDepartmentId(employeeDO.getDepartmentId()); employeeDTO.setDepartmentDO(departmentDO); return employeeDTO; } public void addEmployee(EmployeeDO employeeDO) { if (employeeDO != null && employeeDO.getEmployeeId() == null) { employeeDO.setEmployeeId(generateEmployeeId()); } System.out.println(employeeDO.toString()); employeeDAO.save(employeeDO); } public void updateEmployee(EmployeeDO employeeDO) { employeeDAO.updateEmployeeByEmployeeId(employeeDO.getEmployeeId(), employeeDO.getName(), employeeDO.getGender(), employeeDO.getBirth(), employeeDO.getEmail(), employeeDO.getDepartmentId(), employeeDO.getDescribes()); } public void deleteEmployee(Long employeeId) { employeeDAO.deleteByEmployeeId(employeeId); } private Long generateEmployeeId() { Long maxEmployeeId = employeeDAO.getMaxEmployeeId(); if (maxEmployeeId == null) { maxEmployeeId = 0L; } return ++maxEmployeeId; }}
关于代码内容,这里就不详细展开介绍了。service层的目录结构如下:
2、controller层相关,这里涉及前端页面,内容会稍微多一些
首先我们从Bootstrap官网(https://getbootstrap.com/docs/4.5/examples/)找一个合适的前端页面,定义一个登入页,命名为login.html并放在resources/templates目录下,同时在contoller包中定义一个LoginController类,定义get请求,获取请求登入页,定义登入请求提交登入用户名和密码。在了解代码前,我们先看一下登入页的样式
代码如下:
@Controllerpublic class LoginController { @GetMapping("/login") public String login(){ return "login"; } @PostMapping("/user/login") public String userLogin(@RequestParam("username") String username, @RequestParam("password") String password, Map error, HttpSession session ){ if(!StringUtils.isEmpty(username) && "123456".equals(password)){ session.setAttribute("loginUser",username); return "dashboard"; }else{ error.put("msg","用户名密码错误"); return "login"; } }}
可以看到get请求访问的login方法返回了login字符串,这个字符串会经过springboot模版引擎thymeleaf的解析,去项目的resources/templates目录下找login.html文件,我们看一下login.html中的关键代码:
<form class="form-signin" th:action="@{/user/login}" method="post"> <img class="mb-4" src="/assets/brand/bootstrap-solid.svg" th:src="@{/assets/brand/bootstrap-solid.svg}" alt="" width="72" height="72"> <h1 class="h3 mb-3 font-weight-normal" text="请登入">请登入h1> <p style="color:#ff0000" th:text="${msg}" th:if="${!#strings.isEmpty(msg)}">p> <label class="sr-only" text="用户名">usernamelabel> <input type="email" name="username" class="form-control" placeholder="用户名" required autofocus> <label for="inputPassword" class="sr-only" text="密码">Passwordlabel> <input type="password" id="inputPassword" name="password" class="form-control" placeholder="密码" required> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remeber"/> 记住我 label> div> <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="登入">Sign inbutton> <p class="mt-5 mb-3 text-muted">© 2017-2020p> form>
定义了一个form表单,通过Post的方式(method="post")向user/login(th:action="@{/user/login}")路径发送请求,从而我们LoginController类中通过PostMapping注解接收并处理请求,最终跳转到了dashboard页面。关于login.html文件全部内容,这里就不做介绍了,接下来我们介绍dashboard页面,首先看一下页面样式
首先我用红框框出了顶部栏和侧边栏,这部分因为各个页面一致,所以做为公共元素被提取了出来,下面我们先看一下被提取出来的部分
<html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <title>Titletitle>head><body> <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow" th:fragment="topbar"> <a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">[[${session.loginUser}]]a> <button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon">span> button> <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="#">Sign outa> li> ul> nav> <nav id="sidebar" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse"> <div class="sidebar-sticky pt-3"> <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link active" th:class="${activeUri}=='dashboard.html' ? 'nav-link active' : 'nav-link'" href="#" th:href="@{/dashboard}"> <span data-feather="home">span> 主面板 <span class="sr-only">(current)span> a> li> <li class="nav-item"> <a class="nav-link active" th:class="${activeUri}=='emps'?'nav-link active':'nav-link'" href="#" th:href="@{/emps}"> <span data-feather="users">span> 员工列表 a> li> ul> div> nav>body>html>
首先topbar部分,从session中拿到了登入用户名,并展示([[${session.loginUser}]]),其次是侧边栏sidebar,通过nav-link控制是否高亮
dashboard.html中引入公共的topbar和sidebar的代码
<div th:replace="commons/bar::topbar">div> <div class="container-fluid"> <div class="row"> <div th:replace="commons/bar::#sidebar(activeUri='dashboard.html')">div> div> div>html>
到此为止基本上登入页面的功能就算是实现了。可以看到点击侧边栏的员工列表,向后端发送了/emps的get请求,这个就到了员工列表页面,接下来我们就看一下员工列表页面的逻辑。
关于员工信息的处理,就到我们EmployeeController类登场了
@Controllerpublic class EmployeeController { @Autowired private EmployeeService employeeService; @Autowired private DepartmentService departmentService; /** * 获取员工列表 * * @param model * @return */ @GetMapping("/emps") public String findAllEmployees(Model model) { List allEmployees = employeeService.findAllEmployees(); // 放在请求域中 model.addAttribute("emps", allEmployees); // classpath:/templates/xxx.html return "/emps/list"; } @GetMapping("/emp") public String addEmpPage(Model model) { List allDepartment = departmentService.findAllDepartment(); model.addAttribute("depts", allDepartment); return "/emps/add"; } /** * 添加员工 * * @param employeeDO * @return */ @PostMapping("/emp") public String addEmployee(EmployeeDO employeeDO) { employeeService.addEmployee(employeeDO); return "redirect:/emps"; } /** * 删除员工 * * @param employeeId * @return */ @DeleteMapping("/emp/{employeeId}") public String deleteEmployee(@PathVariable("employeeId") Long employeeId) { employeeService.deleteEmployee(employeeId); return "redirect:/emps"; } @GetMapping("/emp/{employeeId}") public String editEmployeePage(@PathVariable("employeeId") Long employeeId, Model model) { EmployeeDTO employeeDTO = employeeService.findEmployeeById(employeeId); List allDepartment = departmentService.findAllDepartment(); model.addAttribute("depts", allDepartment); model.addAttribute("emp", employeeDTO); return "/emps/add"; } @PutMapping("/emp") public String updateEmployee(EmployeeDO employeeDO){ employeeService.updateEmployee(employeeDO); return "redirect:/emps"; }}
可以看到/emps请求直接查出了所有员工信息,并放入model中,方便页面获取,最后返回字符串/emps/list,这个会被thymeleaf模版引擎解析去找resources/templates/emps/list.html文件,接下来看一下list.html的关键代码
<body><div th:replace="commons/bar::topbar">div><div class="container-fluid"> <div class="row"> <div th:replace="commons/bar::#sidebar(activeUri='emps')">div> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4"> <h2> <a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工a> h2> <div class="table-responsive"> <table class="table table-striped table-sm"> <thead> <tr> <th>employeeIdth> <th>nameth> <th>genderth> <th>departmentth> <th>birthth> <th>describeth> <th>operateth> tr> thead> <tbody> <tr th:each="emp:${emps}"> <td th:text="${emp.employeeId}">td> <td>[[${emp.name}]]td> <td th:text="${emp.gender}==0?'男':'女'">td> <td th:text="${emp.departmentDO.departmentName}">td> <td th:text="${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}">td> <td th:text="${emp.describes}">td> <td> <a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.employeeId}">edita> <button type="submit" th:attr="delete_uri=@{/emp/}+${emp.employeeId}" class="btn btn-danger deleteBtn">deletebutton> td> tr> tbody> table> div> main> <form id="deleteEmpForm" method="post"> <input type="hidden" name="_method" th:value="delete"/> form> div>div><script th:src="@{/assets/dist/js/jquery-3.5.1.js}">script><script src="../assets/dist/js/bootstrap.bundle.min.js" th:src="@{/assets/dist/js/bootstrap.bundle.min.js}">script><script src="https://cdnjs.cloudflare.com/ajax/libs/feather-icons/4.9.0/feather.min.js">script><script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js">script><script th:src="@{/assets/dist/js/dashboard.js}">script><script> $('.deleteBtn').click(function(){ $("#deleteEmpForm").attr("action",$(this).attr("delete_uri")).submit(); return false; });script>body>html>
首先是引入topbar和sidebar,接下来就是获取放入model中的员工信息了,获取方式为:<tr th:each="emp:${emps}">,然后依次展示每个员工信息,同时在删除操作时用js进行实现。
同时我们也在页面上看到了添加和修改员工按钮,接下来我们看一下这些的实现方式。
添加和修改员工分别向后端发送/emp和/emp/{employeeId}的get请求,这两个请求最终都返回了字符串/emps/add,接下来就看一下add.html文件
<body><div th:replace="commons/bar::topbar">div><div class="container-fluid"> <div class="row"> <div th:replace="commons/bar::#sidebar(activeUri='dashboard.html')">div> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4"> <form th:action="@{/emp}" method="post"> <input type="hidden" name="_method" value="put" th:if="${emp != null}"> <input type="hidden" name="employeeId" th:if="${emp != null}" th:value="${emp.employeeId}"> <div class="form-group"> <label>Namelabel> <input name="name" type="text" class="form-control" placeholder="zhangsan" th:value="${emp} !=null ? ${emp.name}"> div> <div class="form-group"> <label>Emaillabel> <input name="email" type="email" class="form-control" placeholder="zhangsan@example.com" th:value="${emp} !=null ? ${emp.email}"> div> <div class="form-group"> <label>genderlabel> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp} !=null ? ${emp.gender==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" th:checked="${emp} !=null ? ${emp.gender==0}"> <label class="form-check-label">男label> div> div> <div class="form-group"> <label>departmentlabel> <select class="form-control" name="departmentId"> <option th:selected="${emp} !=null ? ${dept.departmentId=emp.departmentDO.departmentId}" th:value="${dept.departmentId}" th:each="dept:${depts}" th:text="${dept.departmentName}">1option> select> div> <div class="form-group"> <label>birthlabel> <input type="text" class="form-control" placeholder="2020-01-01" name="birth" th:value="${emp} !=null ? ${#dates.format(emp.birth,'yyyy-MM-dd hh:mm:ss')}">input> div> <div class="form-group"> <label>describelabel> <textarea class="form-control" id="describes" name="describes" rows="3">textarea> div> <button type="submit" class="btn btn-primary" th:text="${emp} !=null ? '修改' : '添加'">添加button> form> main> div>div>body>
也是首先引入topbar和sidebar,新增和修改用的同一个页面,是通过model中有没有emp参数来判断的,有emp信息的为修改,没有的为新增。本次的介绍就到这里吧,详细的代码见github,链接:https://github.com/bestlei/employee-management