專案初始化(IDEA + Maven)
- Create New Project → Maven → 勾選
Create from archetype → webapp(或手動建目錄結構)
GroupId:com.example,ArtifactId:ssm-page-demo
- 等待 IDEA 導入依賴,出現
BUILD SUCCESS
目錄結構標準化
ssm-page-demo
├─ src
│ ├─ main
│ │ ├─ java
│ │ │ └─ com.example.demo
│ │ │ ├─ controller
│ │ │ ├─ service
│ │ │ ├─ dao
│ │ │ ├─ entity
│ │ │ └─ util
│ │ ├─ resources
│ │ │ ├─ mapping # MyBatis XML
│ │ │ ├─ spring-*.xml # Spring 配置
│ │ │ ├─ db.properties
│ │ │ └─ log4j.properties
│ │ └─ webapp
│ │ ├─ WEB-INF
│ │ │ ├─ web.xml
│ │ │ └─ jsp
│ │ └─ static
│ └─ test
└─ pom.xml
核心依賴
<properties>
<spring.version>5.3.25</spring.version>
<mybatis.version>3.5.13</mybatis.version>
<mysql.version>8.0.33</mysql.version>
<druid.version>1.2.20</druid.version>
<pagehelper.version>5.3.3</pagehelper.version>
</properties>
<dependencies>
<!-- Spring 全家桶 -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>${spring.version}</version></dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>${spring.version}</version><scope>test</scope></dependency>
<!-- MyBatis + Spring 整合 -->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>${mybatis.version}</version></dependency>
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.7</version></dependency>
<!-- PageHelper 分頁插件 -->
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>${pagehelper.version}</version></dependency>
<!-- 連接池 Druid -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.20</version></dependency>
<!-- MySQL 驅動 -->
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>${mysql.version}</version></dependency>
<!-- Web 基礎 -->
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency>
<dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency>
<dependency><groupId>taglibs</groupId><artifactId>standard</artifactId><version>1.1.2</version></dependency>
<!-- JSON -->
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.15.2</version></dependency>
<!-- Lombok -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version><scope>provided</scope></dependency>
<!-- 日誌 -->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>2.0.9</version></dependency>
<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j2-impl</artifactId><version>2.20.0</version></dependency>
</dependencies>
關鍵配置文件
db.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/page_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
spring-dao.xml(MyBatis 整合 + PageHelper 註冊)
<beans ...>
<context:property-placeholder location="classpath:db.properties"/>
<!-- Druid 數據源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="initialSize" value="5"/>
<property name="maxActive" value="20"/>
</bean>
<!-- SqlSessionFactory + PageHelper 插件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapping/*.xml"/>
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<value>
helperDialect=mysql
reasonable=true
supportMethodsArguments=true
params=count=countSql
</value>
</property>
</bean>
</array>
</property>
</bean>
<!-- 掃描 DAO 接口 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.demo.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
</beans>
spring-service.xml(事務管理)
<beans ...>
<context:component-scan base-package="com.example.demo.service"/>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
spring-web.xml(MVC 配置)
<beans ...>
<context:component-scan base-package="com.example.demo.controller"/>
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="prettyPrint" value="false"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<mvc:default-servlet-handler/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
web.xml(Servlet 3.1、編碼過濾器、ContextLoader)
<web-app xmlns="https://xmlns.jcp.org/xml/ns/javaee"
version="3.1">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-*.xml</param-value>
</context-param>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param>
<init-param><param-name>forceEncoding</param-name><param-value>true</param-value></init-param>
</filter>
<filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
<listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>
<listener><listener-class>org.springframework.web.context.request.RequestContextListener</listener-class></listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-web.xml</param-value></init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>/</url-pattern></servlet-mapping>
</web-app>
分頁實戰代碼
實體類
@Data @TableName("sys_record")
public class Record {
private Long id;
private String username;
private String loginName;
private String operateDate;
private String business;
private String note;
}
Mapper 接口 + XML
// RecordMapper.java
@Mapper
public interface RecordMapper {
List<Record> selectByLoginName(@Param("loginName") String loginName);
int countByLoginName(@Param("loginName") String loginName);
}
<mapper namespace="com.example.demo.dao.RecordMapper">
<select id="selectByLoginName" resultType="Record">
SELECT * FROM sys_record
WHERE login_name = #{loginName}
ORDER BY operate_date DESC
</select>
<select id="countByLoginName" resultType="int">
SELECT COUNT(1) FROM sys_record WHERE login_name = #{loginName}
</select>
</mapper>
註:無需手寫 LIMIT,PageHelper 會自動攔截並注入分頁 SQL。
Service 層
@Service
public class RecordService {
@Autowired RecordMapper recordMapper;
public PageInfo<Record> queryPage(String loginName, int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize); // 核心:開啟分頁
List<Record> list = recordMapper.selectByLoginName(loginName);
return new PageInfo<>(list); // 封裝總頁數、總條數、導航頁碼等
}
}
Controller
@Controller
@RequestMapping("/record")
public class RecordController {
@Autowired RecordService recordService;
@GetMapping("/list")
public String list(@RequestParam String loginName,
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
Model model) {
PageInfo<Record> page = recordService.queryPage(loginName, pageNum, pageSize);
model.addAttribute("page", page);
return "record/list";
}
}
JSP 分頁導航
<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<table>
<c:forEach items="${page.list}" var="r">
<tr>
<td>${r.id}</td><td>${r.username}</td><td>${r.loginName}</td>
<td>${r.operateDate}</td><td>${r.business}</td><td>${r.note}</td>
</tr>
</c:forEach>
</table>
<div class="pagination">
<c:if test="${page.hasPreviousPage}">
<a href="${pageContext.request.contextPath}/record/list?loginName=${param.loginName}&pageNum=1">首頁</a>
<a href="${pageContext.request.contextPath}/record/list?loginName=${param.loginName}&pageNum=${page.prePage}">上一頁</a>
</c:if>
<span>第 ${page.pageNum} 頁 / 共 ${page.pages} 頁</span>
<c:if test="${page.hasNextPage}">
<a href="${pageContext.request.contextPath}/record/list?loginName=${param.loginName}&pageNum=${page.nextPage}">下一頁</a>
<a href="${pageContext.request.contextPath}/record/list?loginName=${param.loginName}&pageNum=${page.pages}">尾頁</a>
</c:if>
</div>
PageHelper 核心配置解析
| 參數 | 推薦值 | 說明 |
helperDialect | mysql | 明確指定方言,避免自動識別失敗 |
reasonable | true | pageNum<1 時查第一頁,>總頁數時查最後一頁 |
supportMethodsArguments | true | 支援 Mapper 方法參數傳遞 pageNum/pageSize |
params | count=countSql | 優化 Count 查詢(需 XML 定義 countSql) |
Tomcat 部署與驗證
- IDEA → Add Configuration → Tomcat Server → Local
- Deployment 標籤 → + → Artifact → 選擇
ssm-page-demo:war exploded
- 啟動,訪問
https://localhost:8080/record/list?loginName=test
總結與避坑指南
| 坑點 | 解決 |
| PageHelper 不生效 | 檢查 sqlSessionFactory.plugins 是否註冊 PageInterceptor,Mapper 方法是否在 PageHelper.startPage() 之後調用 |
| Count 查詢慢 | 配置 params=count=countSql,在 XML 寫專用 <select id="countSql">SELECT COUNT(1)... |
| JSP EL 表達式不生效 | web.xml version 必須 ≥ 2.4(建議 3.1),JSP 頁面 isELIgnored="false" |
| 中文亂碼 | 全鏈路 UTF-8:DB、連接池、Filter、JSP pageEncoding、Tomcat URIEncoding |
現代化升級建議
- 新專案優先 Spring Boot 3.x + MyBatis-Plus,
@EnablePagination 一行啟用分頁,零 XML。
- 前後端分離架構:Controller 返回
PageResult<T> JSON,前端 Vue/React + Ant Design Table 直接渲染。
- 大數據量深分頁:改用
WHERE id > lastId LIMIT n 游標分頁,避免 OFFSET 掃描大量無用行。