五張表關係的Mermaid ER圖代碼
使用説明
- 關係説明:
||--o{表示一對多關係,中間表與主表的關聯關係通過該符號體現- 最終形成
sys_user(多)↔sys_user_role(中間表)↔sys_role(多)、sys_role(多)↔sys_role_menu(中間表)↔sys_menu(多)的多對多關聯鏈
用户表 ↔ 用户角色關聯表:sys_user.user_id = sys_user_role.user_id,一對多關係(一個用户可關聯多個角色)。
用户角色關聯表 ↔ 角色表:sys_user_role.role_id = sys_role.role_id,多對一關係(多個用户可關聯同一個角色)。
角色表 ↔ 角色菜單關聯表:sys_role.role_id = sys_role_menu.role_id,一對多關係(一個角色可關聯多個菜單)。
角色菜單關聯表 ↔ 菜單權限表:sys_role_menu.menu_id = sys_menu.menu_id,多對一關係(多個角色可關聯同一個菜單)。
- 字段標識:
PK標註主鍵,FK標註外鍵
分頁的實現
@GetMapping("/list")
public TableDataInfo list(SysLogininfor logininfor)
{
// 需要分頁的接口需要加上該函數
startPage();
List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
return getDataTable(list);
}
分頁參數加在url上,後端可通過serlvetUtil獲得
PageHelper 分頁核心實現偽代碼(還原 PageInterceptor 工作流程)
PageInterceptor 如何通過實現 MyBatis Interceptor 接口完成分頁功能:
// 1. 核心攔截器類:實現 MyBatis 的 Interceptor 接口
public class PageInterceptor implements Interceptor {
// 數據庫方言(如 MySQL、Oracle,用於生成對應分頁語法)
private String dialect;
// ====================== 接口實現方法1:setProperties - 初始化配置 ======================
// 讀取配置文件中的分頁屬性(如 dialect=mysql)
@Override
public void setProperties(Properties properties) {
this.dialect = properties.getProperty("helperDialect", "mysql"); // 默認MySQL方言
// 其他配置:如是否自動統計總條數、分頁參數合理化等
}
// ====================== 接口實現方法2:plugin - 生成代理對象 ======================
// 為目標組件(Executor/StatementHandler)生成動態代理,使攔截邏輯生效
@Override
public Object plugin(Object target) {
// 只對需要攔截的組件進行代理(避免無關組件被包裝)
if (target instanceof Executor || target instanceof StatementHandler) {
return Plugin.wrap(target, this); // MyBatis 自帶的代理工具
}
return target; // 非目標組件直接返回
}
// ====================== 接口實現方法3:intercept - 核心分頁邏輯(重點) ======================
@Override
public Object intercept(Invocation invocation) throws Throwable {
// ===== 步驟1:獲取分頁參數(從 ThreadLocal 中獲取,由 PageHelper.startPage() 存入) =====
Page<?> pageParam = PageContext.getLocalPage(); // ThreadLocal 存儲分頁參數
if (pageParam == null) {
// 無分頁參數,直接執行原始SQL,不做分頁處理
return invocation.proceed();
}
// ===== 步驟2:獲取原始SQL及執行相關信息 =====
// 從 invocation 中解析目標對象、方法參數(不同攔截目標解析方式略有差異,此處以 Executor 為例)
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1]; // SQL參數
String originalSql = getOriginalSql(mappedStatement, parameter); // 獲取原始SQL(如:SELECT * FROM sys_user WHERE status = 0)
Integer pageNum = pageParam.getPageNum(); // 當前頁碼(如第2頁)
Integer pageSize = pageParam.getPageSize(); // 每頁條數(如每頁10條)
// ===== 步驟3:動態改寫SQL,生成分頁SQL =====
String pageSql = generatePageSql(originalSql, pageNum, pageSize);
// 示例:MySQL 分頁SQL → SELECT * FROM sys_user WHERE status = 0 LIMIT 10, 10
// ===== 步驟4:自動生成總條數查詢SQL,統計總記錄數 =====
if (pageParam.isCount()) { // 是否需要統計總條數(默認true)
String countSql = generateCountSql(originalSql); // 生成 COUNT SQL:SELECT COUNT(0) FROM (SELECT * FROM sys_user WHERE status = 0) temp
Long totalCount = executeCountSql(countSql, parameter); // 執行COUNT SQL,獲取總條數
pageParam.setTotal(totalCount); // 設置總記錄數
pageParam.setPages(totalCount % pageSize == 0 ? totalCount / pageSize : totalCount / pageSize + 1); // 計算總頁數
}
// ===== 步驟5:替換原始SQL,執行分頁查詢 =====
replaceOriginalSql(mappedStatement, pageSql); // 將改寫後的分頁SQL替換原始SQL
Object pageResult = invocation.proceed(); // 執行分頁SQL,獲取當前頁數據列表
// ===== 步驟6:封裝分頁結果,清除ThreadLocal緩存 =====
pageParam.addAll((List<?>) pageResult); // 將當前頁數據存入分頁對象
PageContext.clearLocalPage(); // 清除ThreadLocal中的分頁參數,避免線程複用導致錯亂
// ===== 步驟7:返回分頁結果(最終可包裝為 PageInfo 對象) =====
return pageParam;
}
// ---------------------- 內部輔助方法:生成分頁SQL ----------------------
private String generatePageSql(String originalSql, Integer pageNum, Integer pageSize) {
if ("mysql".equalsIgnoreCase(dialect)) {
// MySQL 分頁:LIMIT (pageNum-1)*pageSize, pageSize
int offset = (pageNum - 1) * pageSize;
return originalSql + " LIMIT " + offset + ", " + pageSize;
} else if ("oracle".equalsIgnoreCase(dialect)) {
// Oracle 分頁:嵌套ROWNUM查詢
int start = (pageNum - 1) * pageSize + 1;
int end = pageNum * pageSize;
return "SELECT * FROM (SELECT t.*, ROWNUM rn FROM (" + originalSql + ") t WHERE ROWNUM <= " + end + ") WHERE rn >= " + start;
}
// 其他數據庫方言(SQL Server、PostgreSQL等)...
return originalSql;
}
// ---------------------- 內部輔助方法:生成總條數查詢SQL ----------------------
private String generateCountSql(String originalSql) {
// 去除原始SQL中的 ORDER BY(避免統計總條數時排序影響性能)
String sqlWithoutOrderBy = originalSql.replaceAll("ORDER BY.*", "");
return "SELECT COUNT(0) FROM (" + sqlWithoutOrderBy + ") temp_count";
}
// ---------------------- 內部輔助方法:執行COUNT SQL,獲取總條數 ----------------------
private Long executeCountSql(String countSql, Object parameter) {
// 偽代碼:通過JDBC/MyBatis執行COUNT SQL,返回總記錄數
PreparedStatement pstmt = getConnection().prepareStatement(countSql);
setParameters(pstmt, parameter); // 設置SQL參數
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
return rs.getLong(1);
}
return 0L;
}
// 其他輔助方法:獲取原始SQL、替換原始SQL...
private String getOriginalSql(MappedStatement ms, Object parameter) { /* 實現邏輯 */ }
private void replaceOriginalSql(MappedStatement ms, String newSql) { /* 實現邏輯 */ }
}
// ====================== 輔助類:ThreadLocal 存儲分頁參數 ======================
class PageContext {
// ThreadLocal 保證多線程環境下分頁參數隔離
private static final ThreadLocal<Page<?>> PAGE_THREAD_LOCAL = new ThreadLocal<>();
// 設置分頁參數(由 PageHelper.startPage(pageNum, pageSize) 調用)
public static void setLocalPage(Page<?> page) {
PAGE_THREAD_LOCAL.set(page);
}
// 獲取分頁參數
public static Page<?> getLocalPage() {
return PAGE_THREAD_LOCAL.get();
}
// 僅清空當前線程的分頁參數
public static void clearLocalPage() {
PAGE_THREAD_LOCAL.remove();
}
}
// ====================== 分頁參數載體:Page 類(存儲分頁相關信息) ======================
class Page<T> extends ArrayList<T> {
private Integer pageNum; // 當前頁碼
private Integer pageSize; // 每頁條數
private Long total; // 總記錄數
private Integer pages; // 總頁數
private boolean count = true; // 是否統計總條數
// getter/setter 省略
}
// ====================== 外部調用入口:PageHelper 靜態方法 ======================
class PageHelper {
// 開發者調用該方法設置分頁參數,本質是將參數存入 ThreadLocal
public static <T> Page<T> startPage(Integer pageNum, Integer pageSize) {
Page<T> page = new Page<>(pageNum, pageSize);
PageContext.setLocalPage(page);
return page;
}
}
偽代碼核心流程總結
- 參數存入:開發者調用
PageHelper.startPage(pageNum, pageSize),分頁參數存入ThreadLocal,保證多線程隔離; - 攔截觸發:MyBatis 執行 SQL 時,
PageInterceptor攔截目標組件,從ThreadLocal獲取分頁參數; - SQL改寫:根據數據庫方言,動態生成分頁 SQL(如 MySQL 的
LIMIT)和總條數統計 SQL; - 執行查詢:替換原始 SQL,執行分頁查詢和總條數查詢,封裝分頁結果;
- 清理緩存:執行完畢後清除
ThreadLocal中的分頁參數,避免線程複用問題; - 返回結果:返回包含當前頁數據、總條數、總頁數的分頁對象,可進一步包裝為
PageInfo供業務使用。
加油啦!加油鴨,衝鴨!!!
本文章為轉載內容,我們尊重原作者對文章享有的著作權。如有內容錯誤或侵權問題,歡迎原作者聯繫我們進行內容更正或刪除文章。