快速了解
項目簡介
基於 Spring Boot 2.1.0 、 Spring Boot Jpa、 JWT、Spring Security、Redis、Mysql、Vue、Element-UI 的前后端分離的研發過程管理系統, 項目采用按功能分模塊的開發方式,支持數據字典與數據權限管理,支持一鍵生成前后端代碼,支持前端菜單動態路由等。
Spring Boot 2.1.0
后端框架 SpringBoot官網Spring Boot Jpa
Java持久化Api JPA介紹JWT
用戶與服務之間傳遞信息規范 什么是JWT?Spring Security
安全管理框架 spring security——基本介紹Redis
NoSQL數據庫 Redis入門Mysql
數據庫Vue
漸進式框架 VUE介紹Element-UI
組件庫 Element-Ui組件庫地址
此項目用的開源項目Eladmin(2.3版本)
前端地址
https://gitee.com/elunez/eladmin-web
后端地址
后端手冊
項目結構
common
為系統的公共模塊,各種工具類,公共配置存在該模塊system
為系統核心業務模塊也是項目入口模塊,也是最終需要打包部署的模塊logging
為系統的日志模塊,其他模塊如果需要記錄日志需要引入該模塊tools
為第三方工具模塊,包含:圖床、郵件、雲存儲、本地存儲、支付寶generator
為系統的代碼生成模塊,代碼生成的模板在 system 模塊中
詳細結構
- common 公共模塊
- annotation 為系統自定義注解
- aspect 自定義注解的切面
- base 提供了Entity、DTO基類和mapstruct的通用mapper
- config 自定義權限實現、redis配置、swagger配置、Rsa配置等
- exception 項目統一異常的處理
- utils 系統通用工具類
- system 系統核心模塊(系統啟動入口)
- config 配置跨域與靜態資源,與數據權限
- thread 線程池相關
- modules 系統相關模塊(登錄授權、系統監控、定時任務、業務處理邏輯等)
- logging 系統日志模塊
- tools 系統第三方工具模塊
- generator 系統代碼生成模塊
權限控制
本系統安全框架使用的是 Spring Security + Jwt Token
, 訪問后端接口需在請求頭中攜帶token
進行訪問,請求頭格式如下:
# Authorization: Bearer 登錄時返回的token
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU1ODk2NzY0OSwiaWF0IjoxNTU4OTQ2MDQ5fQ.jsJvqHa1tKbJazG0p9kq5J2tT7zAk5B6N_CspdOAQLWgEICStkMmvLE-qapFTtWnnDUPAjqmsmtPFSWYaH5LtA
數據交互
用戶登錄 -> 后端驗證登錄返回 token
-> 前端帶上token
請求后端數據 -> 后端返回數據, 數據交互流程如下:

權限注解
Spring Security
提供了Spring EL
表達式,允許我們在定義接口訪問的方法上面添加注解,來控制訪問權限,常用的 EL
如下
表達式 | 描述 |
---|---|
hasRole([role]) | 當前用戶是否擁有指定角色。 |
hasAnyRole([role1,role2]) | 多個角色是一個以逗號進行分隔的字符串。如果當前用戶擁有指定角色中的任意一個則返回true。 |
下面的接口表示用戶擁有 admin
、menu:edit
權限中的任意一個就能能訪問update
方法, 如果方法不加@preAuthorize
注解,意味着所有用戶都需要帶上有效的 token
后能訪問 update
方法
@Log(description = "修改菜單")
@PutMapping(value = "/menus")
@PreAuthorize("hasAnyRole('admin','menu:edit')")
public ResponseEntity update(@Validated @RequestBody Menu resources){
// 略
}
由於每個接口都需要給超級管理員放行,而使用 hasAnyRole('admin','user:list')
每次都需要重復的添加 admin 權限,因此加入了自定義權限驗證方式,在驗證的時候默認給擁有admin權限的用戶放行。
使用方式:
@PreAuthorize("@el.check('user:list','user:add')")
權限放行
在我們使用的時候,有寫接口是不需要驗證權限,這個時候就需要我們給接口放行,使用方式如下
1、使用注解方式
只需要在Controller的方法上加入該注解即可
@AnonymousAccess
2、修改配置文件方式
system -> modules -> security -> config -> SecurityConfig
TIP
使用
permitAll()
方法所有人都能訪問,包括帶上token
訪問使用
anonymous()
所有人都能訪問,但是帶上token
訪問后會報錯
// 關鍵代碼,部分略
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// 支付寶回調
.antMatchers("/api/aliPay/return").anonymous()
// 所有請求都需要認證
.anyRequest().authenticated();
httpSecurity
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
項目模塊
這里演示使用 Idea 創建一個新的子模塊
新建模塊
選擇 File -> New -> Module

選擇 Maven -> Next

選擇父模塊 -> 定義name -> Finish

回到項目,在新模塊的java目錄下中創建包,包名hundsun.pdpm


TIP
如果包名和 System 模塊 hundsun.pdpm 不一樣,那么需要在 AppRun.class 中配置掃描路徑
需要在 system 模塊中的 AppRun 中配置注解
@ComponentScan(basePackages = {"包名"})
因為
springboot
默認掃描規則是掃描啟動器類的同包或者其子包的下的注解而你新加的模塊的包名與
hundsun.pdpm
不一致,沒有被掃描到會是 404
給新模塊起一個名字

最后在 System 模塊的 pom.xml 的 dependencies 節點加入創建的子模塊
<!--測試模塊-->
<dependency>
<groupId>com.hundsun.pdpm</groupId>
<artifactId>test</artifactId>
<version>2.3</version>
</dependency>

代碼目錄
- domain 數據層,放置與數據庫中表一一對應的JavaBean對象 ,直接進行數據庫的讀寫操作,里面放置DO(Data Object)
- repository 數據庫服務接口
- rest 控制層,處理外部請求,調用service層
- service 業務層,實現業務邏輯,返回的對象需要把DO轉化為DTO(Data Transfer Object 數據傳輸對象)
- dto 放置DTO,用於數據傳遞。(接口入參和接口返回值都可以)
- impl service接口或者mapper接口的實現
- mapper 進行DO和DTO一個轉換操作

區分DO、VO、DTO的好處:
- 防止不需要的字段也會傳遞到前端頁面。
- 可以支持需要轉換的字段。
- 避免某些字段要展示,但是並不希望出現在數據庫中
通用查詢
本項目對 Jpa 的查詢進行了封裝,現可以通過 @Query
注解實現簡單的查詢與復雜查詢
簡單查詢:等於(默認)、大於等於、小於等於、左模糊、右模糊、中模糊、多字段模糊、NOT_EQUAL 、BETWEEN 、NOT_NULL
。
復雜查詢:包含(IN)查詢、左連接、右連接等
參數說明
字段名稱 | 字段描述 | 默認值 |
---|---|---|
propName | 對象的屬性名,如果字段名稱與實體字段一致,則可以省略 | "" |
type | 查詢方式,默認為 | EQUAL |
blurry | 多字段模糊查詢,值為實體字段名稱,僅支持String類型字段 | "" |
joinName | 關聯實體的名稱 | "" |
join | 連接查詢方式,左連接或者右連接 | LEFT |
nvl | 取出來如果為空,返回默認值,僅支持String類型字段 | "" |
使用方式
1、創建一個查詢類 QueryCriteria
@Data
public class QueryCriteria {
// 等於
@Query
private String a;
// 左模糊
@Query(type = Query.Type.LEFT_LIKE)
private String b;
// 右模糊
@Query(type = Query.Type.RIGHT_LIKE)
private String c;
// 大於等於
@Query(type = Query.Type.GREATER_THAN, propName = "createTime")
private Timestamp startTime;
// 小於等於
@Query(type = Query.Type.LESS_THAN, propName = "createTime")
private Timestamp endTime;
// BETWEEN
@Query(type = Query.Type.BETWEEN)
private List<Timestamp> startTime;
// 多字段模糊查詢,blurry 為字段名稱
@Query(blurry = "a,b,c")
private String blurry;
// IN 查詢
@Query(type = Query.Type.IN)
private List<String> d;
// 左關聯查詢,left Join , joinName為關聯實體名稱
@Query(joinName = "")
private String e;
// 右關聯查詢,right Join , joinName為關聯實體名稱
@Query(joinName = "", join = Query.Join.RIGHT)
private String f;
// NOT_EQUAL 不等於
@Query(type = Query.Type.NOT_EQUAL)
private String g;
// NOT_NULL 不為空
@Query(type = Query.Type.NOT_NULL)
private String g;
}
2、在控制器中使用
// Pageable 分頁查詢
public ResponseEntity query(QueryCriteria criteria, Pageable pageable){
return new ResponseEntity(service.queryAll(criteria,pageable), HttpStatus.OK);
}
3、Service 中查詢
@Override
public Object queryAll(QueryCriteria criteria, Pageable pageable){
Page<實體> page = repository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)),pageable);
return page;
}
TIP
如果需要添加一個字段查詢,只需要在查詢類
QueryCriteria
中添加就可以了,可節省大量時間。源碼可以查看
common
模塊中的hundsun.pdpm.annotation.Query
與hundsun.pdpm.utils.QueryHelp
引入模塊
如果在自定義模塊中需要用到common模塊,需要在此模塊的pom.xml中引入common依賴,如圖。
<dependencies>
<dependency>
<groupId>com.hundsun.pdpm</groupId>
<artifactId>common</artifactId>
<version>2.3</version>
</dependency>
</dependencies>

引入后需要點擊右邊Maven中的刷新一下Maven庫

自定義復雜查詢
使用@Query
注解
這個注解的包為:org.springframework.data.jpa.repository.Query
非自定義注解

查詢
@Query(value = "select count(1) from pdpmtest t group by t.teststring",nativeQuery = true)
Integer getCountGroupByTestString();
TIP
加了
nativeQuery = true
可以直接使用sql,如果沒加則需要用hsql,hsql這里不過多贅述,想詳細了解的自行百度一下。
更新/刪除
更新/刪除操作需要增加注解@Modifying
- 更新
@Modifying
@Query(value = "update pdpmtest set teststring = '1'", nativeQuery = true)
void updateAllPdPmTest();
- 刪除
@Modifying
@Query(value = "delete from pdpmtest", nativeQuery = true)
void deleteAllPdPmTest();
使用形參
使用形參有兩種方式
第一種方式:
@Query(value = " select t.* from pdpmtest t where t.id = ?1 or teststring = ?2", nativeQuery = true)
List<PdPmTest> getPdPmTestById(int id,String testString);
?1
和?2
分別指形參id
和testString
第二種方式:
@Query(value = "select t.* from pdpmtest t where t.id in :ids", nativeQuery = true)
List<PdPmTest> getPdPmTestInIdList(@Param("ids") List<Integer> ids);
形參加注解@Param,sql中用:ids
指代
返回類型
第一種:
返回全部對象,則返回類型為具體對象,如:
@Query(value = " select t.* from pdpmtest t where t.id = ?1 or teststring = ?2", nativeQuery = true)
List<PdPmTest> getPdPmTestById(int id,String testString);
第二種:
返回特定列,則返回List<Map<String,Object>>
@Query(value = " select t.testString,t.testNumber from pdpmtest t where t.id = ?1 ", nativeQuery = true)
List<Map<String,Object>> getTestStringAndTestNumber(int id);
前端手冊
目錄結構
- config 配置文件
- src
- api 請求接口
- assets 圖片、logo
- components 封裝的組件
- config 系統全局配置
- icons 圖標
- mixins 混合加載代碼
- router 路由
- store 本地緩存代碼
- styles 樣式
- utils 工具類
- views 業務視圖
- APP.vue 根組件
- main.js 項目的入口文件,實例化Vue;
菜單路由
添加固定菜單
公共的菜單只需要在 src/router/routers.js
中添加就可以了,
如:個人中心頁面
{
path: '/user',//路徑
component: Layout,//組件
hidden: false,//是否隱藏
redirect: 'noredirect',//是否重定向
children: [ //子元素
{
path: 'center',
component: () => import('@/views/system/user/center'),
name: '個人中心',
meta: { title: '個人中心', icon: 'user' }
}
]
}
添加動態菜單

本項目的動態菜單支持到 4級
菜單,支持 外鏈
,支持自定義圖標
,添加教程如下:
(1)添加外鏈
外鏈菜單路由地址必須帶上 https://
或者 http://
,並且外鏈菜單選擇 是

(2)內部菜單
- 外鏈菜單:這個選擇否就好
- 菜單緩存:選擇為
是
那么切換到其他菜單當前菜單會緩存 - 菜單課件:如果不想在左側顯示,可以選擇為
否
- 路由地址:這個就是瀏覽器訪問菜單的地址
- 組件名稱:這個非必填,如果設置了菜單緩存,那么必填,不然緩存會無效
- 組件路徑:項目的組件文件的路徑 src/views
添加內部菜單 | 組件路徑對應 |
---|---|
![]() |
![]() |
分配菜單
創建完菜單還需要在角色管理中給角色分配菜單

權限控制
可以引入權限判斷函數或者使用全局指令函數實現前端的權限控制
1、使用全局指令函數v-permission=""
<!-- 新增 -->
<div v-permission="['admin','user:add']" style="display: inline-block;margin: 0px 2px;">
<el-button
class="filter-item"
size="mini"
type="primary"
icon="el-icon-plus"
@click="add">新增</el-button>
<eForm ref="form" :sup_this="sup_this" :is-add="true" :dicts="dicts"/>
</div>
2、使用判斷函數 checkPermission()
<template>
<el-tab-pane v-if="checkPermission(['admin'])" label="Admin">
admin 權限的用戶才能看到
</el-tab-pane>
</template>
<script>
import checkPermission from '@/utils/permission' // 權限判斷函數
export default{
methods: {
checkPermission
}
}
</script>
數據字典
首先我們需要在字典管理中創建一個字典

數據字典使用一共有兩種方式。
使用全局組件
TIP
建議使用該方式
使用方式:
<template>
<div class="app-container">
</div>
</template>
<script>
export default {
// 設置數據字典
dicts: ['job_status'],
created() {
// 得到完整數據
console.log(this.dict.job_status)
// 打印簡化后的label數據
console.log(this.dict.job_status.label)
}
}
</script>
打印如下:
1、完整數據

2、簡化后的label數據

使用混入方式
源碼位於: src/mixins/initDict.js
,代碼如下
(1)引入組件
import initDict from '@/mixins/initDict'
export default {
mixins: [initDict]
}
(2)使用鈎子函數獲取字典
import initDict from '@/mixins/initDict'
export default {
mixins: [initDict],
created() {
this.$nextTick(() => {
// 加載數據字典
this.getDict('job_status')
})
}
}
(3)使用字典
<el-form-item v-if="form.pid !== 0" label="狀態" prop="enabled">
<el-radio v-for="item in dicts" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
簡單例子
引入logging日志包
因為logging模塊中依賴已經有common模塊,所以如果同時需要common和logging模塊則只需引入logging模塊即可,修改test的pom.xml如下:

測試表
CREATE TABLE `pdpmtest` (
`id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`testString` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '測試String',
`testNumber` decimal(16, 2) NULL DEFAULT 0.00 COMMENT '測試Number',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
后端代碼
目錄總覽

1、定義DO對象--PdPmTest
定義DO對象
@Entity
@Data
@Table(name="pdpmtest")
public class PdPmTest implements Serializable {
@Id
@Column(name = "id")
private Integer id;
@Column(name = "teststring")
private String testString;
@Column(name = "testnumber")
private BigDecimal testNumber;
}
@Entity
代表此對象是與數據庫映射的對象;@Data
注解的主要作用是提高代碼的簡潔,使用這個注解可以省去代碼中大量的get()
、set()
、toString()
等方法;@Table
表名注解,代表與數據庫哪個表映射;@Id
代表此字段為數據庫里的主鍵列@Column
實體類中屬性與數據表中字段的對應關系implements Serializable
實現Serializable
代表序列化
TIP
- 什么是Serializable接口
一個對象序列化的接口,一個類只有實現了Serializable接口,它的對象才能被序列化。
- 什么是序列化?
序列化是將對象狀態轉換為可保持或傳輸的格式的過程。與序列化相對的是反序列化,它將流轉換為對象。這兩個過程結合起來,可以輕松地存儲和傳輸數據。
- 為什么要序列化對象
1、把對象轉換為字節序列的過程稱為對象的序列化
2、把字節序列恢復為對象的過程稱為對象的反序列化
- 什么情況下需要序列化?
當我們需要把對象的狀態信息通過網絡進行傳輸,或者需要將對象的狀態信息持久化,以便將來使用時都需要把對象進行序列化。方便下次使用的時候,可以很快捷的重建一個副本。
2、定義DTO對象--PdPmTestDTO
@Data
public class PdPmTestDTO implements Serializable {
private Integer id;
private String testString;
private BigDecimal testNumber;
private BigDecimal average;
}
3、定義查詢條件對象--PdPmTestQueryCriteria
@Data
public class PdPmTestQueryCriteria {
// 精確
@Query
private String id;
// 模糊
@Query(type = Query.Type.INNER_LIKE)
private String testString;
//大於
@Query(type = Query.Type.GREATER_THAN)
private BigDecimal testNumber;
}
4、定義持久層--PdpmTestRepository
public interface PdPmTestRepository extends JpaRepository<PdPmTest,Long>, JpaSpecificationExecutor<PdPmTest> {
}
-
繼承
JpaRepository<T,ID>
提供了數據訪問功能和一組 JPA 規范相關的方法; -
繼承
JpaSpecificationExecutor<T>
實現一組 JPA Criteria 查詢相關的方法 ;
TIP
其中
T
代表接口返回值類型,ID代表主鍵類型,當復合主鍵時,可以建一個主鍵對象。
5、定義DO轉DTO服務接口-- PdPmTestMapper
@Service
public interface PdPmTestMapper extends BaseMapper<PdPmTestDTO, PdPmTest> {
}
@Service
用在類上,注冊為一個bean,bean名稱默認為類名稱(首字母小寫),也可以手動指定@Service("abc")
或@Service(value = "abc")
6、PdPmTestMapper的實現 -- PdPmTestMapperImpl
@Component
public class PdPmTestMapperImpl implements PdPmTestMapper {
@Override
public PdPmTest toEntity(PdPmTestDTO dto) {
PdPmTest pdPmTest = new PdPmTest();
pdPmTest.setId(dto.getId());
pdPmTest.setTestString(dto.getTestString());
pdPmTest.setTestNumber(dto.getTestNumber());
return pdPmTest;
}
@Override
public PdPmTestDTO toDto(PdPmTest entity) {
PdPmTestDTO pdPmTestDTO = new PdPmTestDTO();
pdPmTestDTO.setId(entity.getId());
pdPmTestDTO.setTestString(entity.getTestString());
pdPmTestDTO.setTestNumber(entity.getTestNumber());
//求testNumber與id的平均值
BigDecimal testNumber = entity.getTestNumber() == null ? BigDecimal.ZERO : entity.getTestNumber();
pdPmTestDTO.setAverage(testNumber.divide(new BigDecimal(entity.getId()), BigDecimal.ROUND_UP));
return pdPmTestDTO;
}
@Override
public List<PdPmTest> toEntity(List<PdPmTestDTO> dtoList) {
List<PdPmTest> pdPmTests = new ArrayList<>();
dtoList.forEach(pdPmTestDTO -> {
pdPmTests.add(toEntity(pdPmTestDTO));
});
return pdPmTests;
}
@Override
public List<PdPmTestDTO> toDto(List<PdPmTest> entityList) {
List<PdPmTestDTO> pdPmTests = new ArrayList<>();
entityList.forEach(pdPmTestDTO -> {
pdPmTests.add(toDto(pdPmTestDTO));
});
return pdPmTests;
}
}
@Component
實現bean的注入
7、定義業務層--PdPmTestService
@Service
public interface PdPmTestService {
/**
* 查詢所有信息根據查詢條件
*
* @param criteria 查詢條件
* @return 查詢結果
* @author yantt21019
* @date 2020/8/17
*/
List<PdPmTestDTO> queryAllByCriteria(PdPmTestQueryCriteria criteria);
}
8、業務層實現定義--PdPmTestServiceImpl
@Component
public class PdPmTestServiceImpl implements PdPmTestService {
PdPmTestRepository pdPmTestRepository;
@Autowired
PdPmTestMapper pdPmTestMapper;
@Autowired
EntityManager entityManager;
PdPmTestServiceImpl(PdPmTestRepository pdPmTestRepository){
this.pdPmTestRepository = pdPmTestRepository;
}
@Override
public List<PdPmTestDTO> queryAllByCriteria(PdPmTestQueryCriteria criteria) {
Specification<PdPmTest> specification = new Specification<PdPmTest>() {
private static final long serialVersionUID = 1L;
@Override
public Predicate toPredicate(Root<PdPmTest> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return QueryHelp.getPredicate(root,criteria,criteriaBuilder);
}
};
List<PdPmTest> testRepositoryAll = pdPmTestRepository.findAll(specification);
return pdPmTestMapper.toDto(testRepositoryAll);
}
@Autowired
注解,它可以對類成員變量、方法及構造函數進行標注,完成自動裝配的工作;Specification
為Spring Data Jpa的規范對象,如果用規范封裝查詢就必須創建Specification對象並對查詢條件;
9、定義控制層--PdPmTestController
@Controller
@RequestMapping("/api/PdPmTest")
public class PdPmTestController {
@Autowired
PdPmTestService pdPmTestService;
@GetMapping(value = "/getTests")
@Log("查詢測試信息")
@PreAuthorize("@el.check('test:list')")
public ResponseEntity getTests(PdPmTestQueryCriteria criteria){
return new ResponseEntity(pdPmTestService.queryAllByCriteria(criteria), HttpStatus.OK);
}
}
@Controller
用於標記在一個類上,使用它標記的類就是一個SpringMvc Controller對象,分發處理器會掃描使用該注解的類的方法,並檢測該方法是否使用了@RequestMapping
注解;@RequestMapping
來映射請求,也就是通過它來指定控制器可以處理哪些URL請求;@GetMapping
獲取get請求,value值為指定請求的實際地址;ResponseEntity
為處理Http響應的返回值,ResponseEntity(@Nullable T body, HttpStatus status),第一個參數為,請求實體內容,第二個為請求返回狀態;
前端代碼
新增測試文件目錄
在src/views/下新建test目錄

新增VUE文件

新增testindex.vue文件

template
標簽為模板標簽,里面存放組件元素;script
標簽為用於定義客戶端腳本,比如 JavaScript;style
標簽用於定義樣式信息;
添加testindex到固定路由展示
,
{
path: '/test',
component: Layout,
hidden: false,
redirect: 'noredirect',
children: [
{
path: 'testindex',
component: () => import('@/views/test/testindex'),
name: '測試界面',
meta: { title: '測試界面', icon: 'user' }
}
]
}

啟動前后台看一下測試界面

現在測試界面還是空空如也
增加表格展示從數據庫查詢到的數據
1、打開Element-Ui組件庫網址

2、找到表格組件

3、選取需要的樣式點擊顯示代碼,並復制到對應的地方


看一下測試界面

測試界面已經多了一個表格
但是我們發現這個表格的數據都是固定,字段也不符合要求。
4、修改表格組件顯示字段
<el-table
:data="tableData"
style="width: 100%">
<el-table-column
prop="id"
label="測試id"
width="180">
</el-table-column>
<el-table-column
prop="testString"
label="測試String"
width="180">
</el-table-column>
<el-table-column
prop="testNumber"
label="測試數字">
</el-table-column>
<el-table-column
prop="average"
label="平均值">
</el-table-column>
</el-table>
由於此時數據需要從數據庫查詢則需要新增api接口
5、新增api文件和查詢接口
在src/api/目錄下

新建test.js文件,並新增查詢請求

import request from '@/utils/request'
export function getAllTests(params) {
return request({
url: '/api/PdPmTest/getTests',
method: 'get',
params
})
}
6、在testindex文件中導入查詢接口,並在創建界面時查詢一次數據庫

import { getAllTests } from '@/api/test'
引入創建鈎子函數,這個created會在界面創建時調用一次

調用查詢函數並且賦值給tableData

created() {
getAllTests().then(res=>{
this.tableData = res.content;
}).catch(e=>{
console.log(e)
})
}
7、插入數據庫數據
INSERT INTO `pdpmtest` VALUES (1, '測試1', 123456.78);
INSERT INTO `pdpmtest` VALUES (2, '測試2', 66666.66);
例子進階需求
實現以下需求:
- 測試界面增加查詢輸入框、查詢和重置按鈕;
- 增加新增、修改和刪除按鈕;
- 實現新增、修改和刪除功能;
參考:
前端參考:
--testindex可以參考
src/views/business/product/index.vue
-- 新增的form可以參考
src/views/business/product/form.vue
-- api參考
src/api/product.js
后端參考:
-- service接口參考
hundsun/pdpm/modules/system/service/ProductService.java
--service實現參考
hundsun/pdpm/modules/system/service/impl/ProductServiceImpl.java
-- controller接口參考
hundsun/pdpm/modules/system/rest/ProductController.java
如何看錯誤信息
看如下錯誤信息:
pdpm- 2020-08-18 15:26:36 [http-nio-8000-exec-8] ERROR h.p.e.handler.GlobalExceptionHandler - java.lang.NullPointerException
at hundsun.pdpm.service.impl.PdPmTestMapperImpl.toDto(PdPmTestMapperImpl.java:48)
at hundsun.pdpm.service.impl.PdPmTestServiceImpl.queryAllByCriteria(PdPmTestServiceImpl.java:65)
at hundsun.pdpm.rest.PdPmTestController.getTests(PdPmTestController.java:32)
at hundsun.pdpm.rest.PdPmTestController$$FastClassBySpringCGLIB$$577cefa1.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
at hundsun.pdpm.aspect.LogAspect.logAround(LogAspect.java:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:69)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
at hundsun.pdpm.rest.PdPmTestController$$EnhancerBySpringCGLIB$$997650fb.getTests(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:215)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:142)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:998)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:890)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:875)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.jasig.cas.client.util.AssertionThreadLocalFilter.doFilter(AssertionThreadLocalFilter.java:50)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.jasig.cas.client.util.HttpServletRequestWrapperFilter.doFilter(HttpServletRequestWrapperFilter.java:70)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at hundsun.pdpm.modules.security.security.JwtAuthorizationTokenFilter.doFilterInternal(JwtAuthorizationTokenFilter.java:60)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:770)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
1、看錯誤信息首先看第一行,一般第一行會告訴我們確切的錯誤原因
java.lang.NullPointerException
這個錯誤原因顯而易見是空指針
2、接着去在這大長串的錯誤棧信息里找屬於我們自己包的第一條!
at hundsun.pdpm.service.impl.PdPmTestMapperImpl.toDto(PdPmTestMapperImpl.java:48)
這個是說明在PdPmTestMapperImpl
文件中toDto
函數,48
代表在文件PdPmTestMapperImpl.java
第48
行;
一般第一個出現我們自己包的錯誤是改錯誤發生的位置,但不一定是錯誤真的原因位置我們需要打斷點去自己排查

因為此入參是傳過來的,所以接着看錯誤信息第二行:
at hundsun.pdpm.service.impl.PdPmTestServiceImpl.queryAllByCriteria(PdPmTestServiceImpl.java:65)
說明在PdPmTestServiceImpl.java
的第65
行調用了剛剛的函數;
可以看到代碼因為我想故意使他報錯,所以真正的原因是此變量被強行賦值為null了;
基礎知識
常用框架
Spring
什么是Spring?
我們一般說 Spring 框架指的都是 Spring Framework,它是很多模塊的集合,使用這些模塊可以很方便地協助我們進行開發。這些模塊是:核心容器、數據訪問/集成,、Web、AOP(面向切面編程)、工具、消息和測試模塊。比如:Core Container 中的 Core 組件是Spring 所有組件的核心,Beans 組件和 Context 組件是實現IOC和依賴注入的基礎,AOP組件用來實現面向切面編程。
Spring 官網列出的 Spring 的 6 個特征:
- 核心技術 :依賴注入(DI),AOP,事件(events),資源,i18n,驗證,數據綁定,類型轉換,SpEL。
- 測試 :模擬對象,TestContext框架,Spring MVC 測試,WebTestClient。
- 數據訪問 :事務,DAO支持,JDBC,ORM,編組XML。
- Web支持 : Spring MVC和Spring WebFlux Web框架。
- 集成 :遠程處理,JMS,JCA,JMX,電子郵件,任務,調度,緩存。
- 語言 :Kotlin,Groovy,動態語言。
Spring有哪些模塊?

- Spring Core: 基礎,可以說 Spring 其他所有的功能都需要依賴於該類庫。主要提供 IoC 依賴注入功能。
- Spring Aspects : 該模塊為與AspectJ的集成提供支持。
- Spring AOP :提供了面向切面的編程實現。
- Spring JDBC : Java數據庫連接。
- Spring JMS :Java消息服務。
- Spring ORM : 用於支持Hibernate等ORM工具。
- Spring Web : 為創建Web應用程序提供支持。
- Spring Test : 提供了對 JUnit 和 TestNG 測試的支持
IoC
IoC(Inverse of Control:控制反轉)是一種設計思想,就是 將原本在程序中手動創建對象的控制權,交由Spring框架來管理。 IoC 在其他語言中也有應用,並非 Spring 特有。 IoC 容器是 Spring 用來實現 IoC 的載體, IoC 容器實際上就是個Map(key,value),Map 中存放的是各種對象。
將對象之間的相互依賴關系交給 IoC 容器來管理,並由 IoC 容器完成對象的注入。這樣可以很大程度上簡化應用的開發,把應用從復雜的依賴關系中解放出來。 IoC 容器就像是一個工廠一樣,當我們需要創建一個對象的時候,只需要配置好配置文件/注解即可,完全不用考慮對象是如何被創建出來的。 在實際項目中一個 Service 類可能有幾百甚至上千個類作為它的底層,假如我們需要實例化這個 Service,你可能要每次都要搞清這個 Service 所有底層類的構造函數,這可能會把人逼瘋。如果利用 IoC 的話,你只需要配置好,然后在需要的地方引用就行了,這大大增加了項目的可維護性且降低了開發難度。
Spring 時代之前一般通過 XML 文件來配置 Bean,后來開發人員覺得 XML 文件來配置不太好,於是 SpringBoot 注解配置就慢慢開始流行起來。
推薦閱讀:https://www.zhihu.com/question/23277575/answer/169698662
IoC源碼閱讀
AOP
AOP(Aspect-Oriented Programming:面向切面編程)能夠將那些與業務無關,卻為業務模塊所共同調用的邏輯或責任(例如事務處理、日志管理、權限控制等)封裝起來,便於減少系統的重復代碼,降低模塊間的耦合度,並有利於未來的可拓展性和可維護性。
Spring AOP就是基於動態代理的,如果要代理的對象,實現了某個接口,那么Spring AOP會使用JDK Proxy,去創建代理對象,而對於沒有實現接口的對象,就無法使用 JDK Proxy 去進行代理了,這時候Spring AOP會使用Cglib ,這時候Spring AOP會使用 Cglib 生成一個被代理對象的子類來作為代理。
使用 AOP 之后我們可以把一些通用功能抽象出來,在需要用到的地方直接使用即可,這樣大大簡化了代碼量。我們需要增加新功能時也方便,這樣也提高了系統擴展性。日志功能、事務管理等等場景都用到了 AOP 。
Mybatis
#{}和${}的區別是什么?
${}
是 Properties 文件中的變量占位符,它可以用於標簽屬性值和 sql 內部,屬於靜態文本替換,比如${driver}會被靜態替換為com.mysql.jdbc.Driver
。#{}
是 sql 的參數占位符,Mybatis 會將 sql 中的#{}
替換為?號,在 sql 執行前會使用 PreparedStatement 的參數設置方法,按序給 sql 的?號占位符設置參數值,比如 ps.setInt(0, parameterValue),#{item.name}
的取值方式為使用反射從參數對象中獲取 item 對象的 name 屬性值,相當於param.getItem().getName()
。
Xml 映射文件中,除了常見的 select|insert|updae|delete 標簽之外,還有哪些標簽?
<resultMap>
、<parameterMap>
、<sql>
、<include>
、<selectKey>
,加上動態 sql 的 9 個標簽,trim|where|set|foreach|if|choose|when|otherwise|bind
等,其中為 sql 片段標簽,通過<include>
標簽引入 sql 片段,<selectKey>
為不支持自增的主鍵生成策略標簽。
Java基礎
面向對象和面向過程的區別
-
面向過程 :面向過程性能比面向對象高。 因為類調用時需要實例化,開銷比較大,比較消耗資源,所以當性能是最重要的考量因素的時候,比如單片機、嵌入式開發、Linux/Unix 等一般采用面向過程開發。但是,面向過程沒有面向對象易維護、易復用、易擴展。
-
面向對象 :面向對象易維護、易復用、易擴展。 因為面向對象有封裝、繼承、多態性的特性,所以可以設計出低耦合的系統,使系統更加靈活、更加易於維護。但是,面向對象性能比面向過程低。
Java 和 C++的區別?
- 都是面向對象的語言,都支持封裝、繼承和多態
- Java 不提供指針來直接訪問內存,程序內存更加安全
- Java 的類是單繼承的,C++ 支持多重繼承;雖然 Java 的類不可以多繼承,但是接口可以多繼承。
- Java 有自動內存管理機制,不需要程序員手動釋放無用內存
- 在 C 語言中,字符串或字符數組最后都會有一個額外的字符‘\0’來表示結束。但是,Java 語言中沒有結束符這一概念。
== 與 equals(重要)
== : 它的作用是判斷兩個對象的地址是不是相等。即,判斷兩個對象是不是同一個對象(基本數據類型比較的是值,引用數據類型比較的是內存地址)。
equals() : 它的作用也是判斷兩個對象是否相等。但它一般有兩種使用情況:
- 情況 1:類沒有覆蓋 equals() 方法。則通過 equals() 比較該類的兩個對象時,等價於通過“==”比較這兩個對象。
- 情況 2:類覆蓋了 equals() 方法。一般,我們都覆蓋 equals() 方法來比較兩個對象的內容是否相等;若它們的內容相等,則返回 true (即,認為這兩個對象相等)。
舉個例子:
public class test1 {
public static void main(String[] args) {
String a = new String("ab"); // a 為一個引用
String b = new String("ab"); // b為另一個引用,對象的內容一樣
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 從常量池中查找
if (aa == bb) // true
System.out.println("aa==bb");
if (a == b) // false,非同一對象
System.out.println("a==b");
if (a.equals(b)) // true
System.out.println("aEQb");
if (42 == 42.0) { // true
System.out.println("true");
}
}
}
說明:
- String 中的 equals 方法是被重寫過的,因為 object 的 equals 方法是比較的對象的內存地址,而 String 的 equals 方法比較的是對象的值。
- 當創建 String 類型的對象時,虛擬機會在常量池中查找有沒有已經存在的值和要創建的值相同的對象,如果有就把它賦給當前引用。如果沒有就在常量池中重新創建一個 String 對象。
Java集合
List,Set,Map三者的區別?
- List(對付順序的好幫手): List接口存儲一組不唯一(可以有多個元素引用相同的對象),有序的對象
- Set(注重獨一無二的性質): 不允許重復的集合。不會有多個元素引用相同的對象。
- Map(用Key來搜索的專家): 使用鍵值對存儲。Map會維護與Key有關聯的值。兩個Key可以引用相同的對象,但Key不能重復,典型的Key是String類型,但也可以是任何對象。
常用的Map集合
如何選用集合?
主要根據集合的特點來選用,比如我們需要根據鍵值獲取到元素值時就選用Map接口下的集合,需要排序時選擇TreeMap,不需要排序時就選擇HashMap,需要保證線程安全就選用ConcurrentHashMap.當我們只需要存放元素值時,就選擇實現Collection接口的集合,需要保證元素唯一時選擇實現Set接口的集合比如TreeSet或HashSet,不需要就選擇實現List接口的比如ArrayList或LinkedList,然后再根據實現這些接口的集合的特點來選用。