目錄
0、背景
因爲種種原因,我現在計劃用SpringBoot搭建一個網站平臺,由於非科班出生,從頭系統學的成本太大了,而且也不是很必要,因此決定以項目驅動學習,先在做一個小demo(在網頁端操作數據庫,實現增刪查改)。我之前有過做一個網站的課程設計,前端用的jsp,後端沒有用什麼框架,連數據用的jdbc,總體而言就是一個小菜雞水平。
我請了一個軟工的外援來幫我,學習路線就是:他大概給了講了springboot的總體思想,代碼中幾個重要的部分(我其實並沒有太聽懂),然後發給了一個他的課程設計的源碼,我啃源碼,然後動手做自己的demo,遇到問題再問他。
寫這篇博客的目的是爲了做筆記,一方面方便日後回顧,再者也可以加深理解,如果可以幫助到一些和我一樣的入門級小菜雞就更好啦。我寫博客的風格就是詳細,博客寫完了我會讓軟工大佬幫忙看一下,確保沒有知識點上的差錯誤人子弟,然後項目源碼也會共享滴。
然後,在項目開始前,我已經安裝好了Java,maven,mysql。
一、STS環境安裝、配置
STS下載鏈接,我這裏下載的是STS 3.9.10
下載好了就可以啓動這個啦。
啓動後,首先選擇工作空間。
配置jre和maven。
windows --> preferences --> java --> installed jre
選自己的Jre目錄,然後finish後勾上。
配置maven。
我已經安裝好maven了,這裏就是選maven的setting.xml的路徑
截止到這一步,配置就完事了。
----------------------------------------------------------------------------------------------------------------------
二、建一個project並完成基本配置
new-->new Spring Starter Project
不用設置什麼,直接next,然後勾選需要的依賴,然後finish。
然後就可以看到新建好的項目了,簡單的看一下目錄。
DemoApplication.java:啓動類
application.properties:spring boot配置文件
pom.xml:maven配置文件
配置pom.xml
因爲我的前端是用jsp,所以要配置一下Jsp,然後在原本生成的配置中還加了一些插件。完整的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.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project 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>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 對jsp的支持 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.example.demo.DemoApplication</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/webapp</directory>
<targetPath>META-INF/resources</targetPath>
<includes>
<include>**/**</include>
</includes>
</resource>
</resources>
</build>
</project>
接下來配置application.properties:
其中springbootDB是我的Mysql數據庫名,在spring.datasource.url需要改一下哈。
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springbootDB?serverTimezone=Hongkong
spring.datasource.username=root
spring.datasource.password=lemon
mybatis.mapper-locations=mybatis/mapper/*Mapper.xml
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
然後run一下看看有配置上有沒有什麼問題。
果不其然,報錯了。Failed to configure a DataSource: 'url' attribute is not specified and no embedd
修改辦法:
右擊項目,build path >> configure build path
把**remove掉就可以了。
成功運行的界面如下:
截止到現在,配置上就完事了。
三、啃源碼
啃源碼的第一步就是認識目錄,一共分爲7大塊吧。這一步我也不打算每一句都啃透
我的小目標是:①明確項目結構(7大塊),②各部分大致功能,③各部分大致長啥樣。
這裏我會貼部分朋友的課程設計的代碼,不需要看懂裏面的邏輯,大概看一下長啥樣就行,具體要自己做時,再依葫蘆畫瓢吧。
1.啓動類ExamingApplication
這個建項目的時候自動生成了的,點進去一看,居然沒做任何修改。
2.工具類
這個是非必須的。
可能有些功能需要有複雜的邏輯處理,然後又會被調用很多次,那就封裝成一個工具類咯,方便重複調用。
3.實體類
這個是比較好理解的,建立實體類,再給每個屬性創建getter和setter。
package com.dn.examing.pojo;
import java.io.Serializable;
public class User implements Serializable{
private Long id;
private String username;
private String password;
private String phone;
private Integer age;
private Integer gender;
private Integer type;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
}
4.Mapper
mapper裏面全是一些接口,在這裏直接操作數據庫,這裏用到了註解。
我以前沒接觸過註解,但是看樣子,註解的內容就是一些sql語句,用到了再詳細查吧。(好像並不難的亞子)
package com.dn.examing.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import com.dn.examing.pojo.User;
@Mapper
public interface UserMapper {
@Select("SELECT * FROM tb_user WHERE username=#{username} AND password=#{password}")
User selectUser(User form);
@Select("SELECT username FROM tb_user WHERE username=#{username}")
String selectUserNameByUsername(String username);
@Insert("INSERT tb_user VALUES(#{id},#{username},#{password},#{phone},#{age},#{gender},#{type})")
void insertUser(User form);
@Select("SELECT password FROM tb_user WHERE username=#{username} AND phone=#{phone}")
String selectPassword(User form);
@Update("UPDATE tb_user SET password=#{password},phone=#{phone},age=#{age},gender=#{gender} "
+ "WHERE id=#{id}")
void updateUser(User form);
}
5.Service
這裏就是正兒八經的邏輯處理了, 以用戶登錄爲例的話,就包括了根據前端的輸入(但是Service並不會直接去接受前端的輸入哦!),然後去數據庫裏面查詢密碼是否正確之類的,這些業務處理都是在Service裏面,Service就像一個幹實事的部分。
然後還可以看到,Service會藉助Mapper操作數據庫。
package com.dn.examing.service;
import java.io.UnsupportedEncodingException;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.dn.examing.mapper.UserMapper;
import com.dn.examing.pojo.User;
@Service
@Transactional
public class UserService {
@Autowired
private UserMapper userMapper;
public User selectUser(User form) throws Exception {
form.setPassword(new String(Base64.encodeBase64(form.getPassword().getBytes("UTF-8"))));
User user = userMapper.selectUser(form);
if(user == null) {
return user;
}
user.setPassword(new String(Base64.decodeBase64(user.getPassword().getBytes("UTF-8"))));
return user;
}
public boolean selectUserNameByUsername(String username) {
String user = userMapper.selectUserNameByUsername(username);
if(user == null || user.isEmpty()) {
return true;
}
return false;
}
public void insertUser(User form) throws Exception {
form.setPassword(new String(Base64.encodeBase64(form.getPassword().getBytes("UTF-8"))));
userMapper.insertUser(form);
}
public String selectPassword(User form) throws Exception {
String password = userMapper.selectPassword(form);
if(password == null) {
return password;
}else {
password = new String(Base64.decodeBase64(password.getBytes("UTF-8")));
return password;
}
}
public void updateUser(User form) throws Exception {
form.setPassword(new String(Base64.encodeBase64(form.getPassword().getBytes("UTF-8"))));
userMapper.updateUser(form);
form.setPassword(new String(Base64.decodeBase64(form.getPassword().getBytes("UTF-8"))));
}
}
6.Controller
controller是從前端接收數據,然後把接受的數據交給Service進行處理,然後把Service的處理結果呈現到前端。
從代碼片中可以看到,return "homepage";,這裏的homepage都是Jsp網頁,controller是直接的前端打交道的。
package com.dn.examing.controller;
import javax.servlet.http.HttpSession;
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 com.dn.examing.common.Utils;
import com.dn.examing.pojo.User;
import com.dn.examing.service.UserService;
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/login")
public String login(Model model,User form,HttpSession session) throws Exception {
User user = userService.selectUser(form);
if(user == null) {
model.addAttribute("msg","用戶名或密碼錯誤");
return "login";
}else {
session.setAttribute("session_user", user);
if(user.getType()==0) {
return "back/homepage";
}else {
return "homepage";
}
}
}
@RequestMapping("/register")
public String register(Model model,User form) throws Exception {
if(form.getUsername()==null || form.getUsername().length()<4 || form.getUsername().length()>10) {
model.addAttribute("msg","用戶名長度必須在4-10位之間");
return "register";
}
if(form.getPassword()==null || form.getPassword().length()<4 || form.getPassword().length()>10) {
model.addAttribute("msg","密碼長度必須在4-10位之間");
return "register";
}
if(form.getPhone()==null || form.getPhone().length()!=11) {
model.addAttribute("msg","電話號碼必須爲11位");
return "register";
}
if(!userService.selectUserNameByUsername(form.getUsername())) {
model.addAttribute("msg","用戶名已存在");
return "register";
}
form.setId(Utils.makeId());
form.setType(1);
userService.insertUser(form);
return "login";
}
@RequestMapping("/findPassword")
public String findPassword(Model model,User form) throws Exception {
String password = userService.selectPassword(form);
if(password == null || password.isEmpty()) {
model.addAttribute("msg","用戶名和手機號與註冊時不一致");
}else {
model.addAttribute("msg","您的密碼爲:"+password);
}
return "findPassword";
}
@RequestMapping("/editUser")
public String editUser(Model model,User form,HttpSession session) throws Exception {
if(form.getPassword()==null || form.getPassword().length()<4 || form.getPassword().length()>10) {
model.addAttribute("msg","密碼長度必須在4-10位之間");
return "editUser";
}
if(form.getPhone()==null || form.getPhone().length()!=11) {
model.addAttribute("msg","電話號碼必須爲11位");
return "editUser";
}
User user = (User)session.getAttribute("session_user");
form.setUsername(user.getUsername());
form.setId(user.getId());
form.setType(user.getType());
userService.updateUser(form);
session.setAttribute("session_user", form);
if(form.getType()==0) {
return "back/homepage";
}else {
return "homepage";
}
}
@RequestMapping("/exitLogin")
public String exitLogin(HttpSession session) throws Exception {
session.removeAttribute("session_user");
return "login";
}
}
7.jsp
jsp網頁,也就是前端部分。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/css/bootstrap-theme.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/css/bootstrap.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/js/affix.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/js/alert.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/js/bootstrap.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/js/button.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/js/carousel.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/js/collapse.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/js/dropdown.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/js/modal.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/js/popover.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/js/scrollspy.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/js/tab.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/js/tooltip.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.0.1/js/transition.js"></script>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<h3 class="text-center">
用戶登錄
</h3>
<form role="form" action="/login" method="post">
<div class="form-group">
<label>用戶名</label>
<input type="text" class="form-control" name="username" />
</div>
<div class="form-group">
<label>密碼</label>
<input type="password" class="form-control" name="password" />
</div>
<button type="submit" class="btn btn-default">登錄</button>
</form>
<h3 class="text-info">
<a href="/toRegister">點擊此處前往註冊</a>
</h3>
<h3 class="text-info">
<a href="/toFindPassword">找回密碼</a>
</h3>
<h3 class="text-center text-warning">
${msg }
</h3>
</div>
</div>
</div>
</body>
</html>
啃源碼的第二部就是掌握各部分是怎麼聯動起來的。
以用戶登錄爲例子,串一串各部分是怎麼連動起來的叭。
1.login.jsp裏面有一個表單,有兩個input的name分別是username和password,輸入用戶名和密碼。提交表單會執行/login
2.一旦表單提交了,就要執行/login,因爲註解的綁定,對應的controller就要開始幹活了。
該方法有一個參數User form,它會自動和表單裏面的username和password匹配上,爲什麼會自己匹配上呢,是因爲User裏面有username和password這兩個屬性,由於名稱上的嚴格一致,他們就像磁鐵一樣的匹配上了。
然後再login方法裏面,會創建一個UserService的對象,調用裏面的selectUser方法幹實事,看看數據庫裏面有沒有這個用戶。如果有,去homepage頁面,如果沒有,去login頁面,這裏return的都是jsp頁面。
3.在UserController裏面,調用了UserService。
UserService會根據傳入的參數,去數據庫裏面進行查找,根據查找結果進行一些邏輯處理,然後返回處理後的結果。
具體執行查詢動作,會調用UserMapper的方法。
4.mapper就是一個沒有感情的數據庫操作員,裏面全是接口,然後註解裏面是Sql語句。mapper會返回數據庫裏找到的東西。
5.然後mapper裏面的東西傳給service,service裏面的東西傳給controller,最後controller控制前端的跳轉。這裏的return都是jsp頁面。
整個流程大概如下:
因此是第一次接觸SpringBoot,也是第一次接觸框架,所以在瞭解這個用戶登錄過程,我遇到了如下問題:
Q1:UserController是一個類,但是在整個項目代碼裏面也沒有創建一個它的對象去顯式的調用它的login方法,login方法怎麼被調用的呢??
A1:大佬給我說其實是底層DispatcherServlet調用的,只不過被封裝了看不到。再能看到的代碼中,用戶登錄表單的action:/login,和UserController中的login方法由於@RequestMapping("/login")註解聯繫了起來。
Q2:登錄按鈕都沒有設置點擊監聽器,怎麼被點擊了就會執行對應的方法呢?
A2:我已經太久沒碰前端了,對錶單也是極其不熟悉啊!!form表單中有一個button type="submit",然後表單action="/login" method="post",表單被提交了,自動會跳轉到/login。
Q3:前端傳入的數據(username,password)怎麼就被UserController中的login方法接收到了呢??
A3:login方法的參數有User form,其中user有username,password屬性,由於二者名稱上的嚴格一致就對應上了。真的很神奇。也可以不是User類,String也行的,只要是變量名稱和username,password嚴格對應就行。
截止到現在,對SpringBoot的模塊和運行流程有個大概瞭解了,明天開始自己動手做demo。衝鴨!
四、demo設計
因爲主要是我爲了摸清楚Spring boot,所以前端就沒有任何美工了,非常的簡單粗暴。
這個demo呢,是一個班級人員管理,實體就是Student(id,name),然後在不同的界面實現班級人員的增刪查改。
homepage.jsp
主頁四個button,點擊後跳到響應的網頁。(這裏就算練習了頁面跳轉)
insert.jsp
delete.jsp
select.jsp
update.jsp
具體的操作情況,簡單得我說你都能猜到哈哈哈。
五、demo編程實現
1.數據庫建表
2.編寫實體類
package com.example.demo.pojo;
public class Student {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3.寫前端代碼