系列最終篇:引入 Hibernate ORM 替換手寫持久化,配合 Spring 容器 管理 Bean 生命週期與事務,實現生產級計數器:分層清晰、配置驅動、支援集群、可擴展緩存。

@Entity @Table(name = "counter")
public class Counter {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "count_value") private long countValue;
@Version private int version; // 樂觀鎖
// getter/setter
}
@Repository
public class CounterDao {
@Autowired private SessionFactory sessionFactory;
private Session getSession() { return sessionFactory.getCurrentSession(); }
public Counter findForUpdate() {
return (Counter) getSession().createQuery(
"FROM Counter WHERE id = 1").setLockMode(LockMode.PESSIMISTIC_WRITE).uniqueResult();
}
public void saveOrUpdate(Counter c) { getSession().saveOrUpdate(c); }
}
@Service @Transactional
public class CounterService {
@Autowired private CounterDao counterDao;
public long incrementAndGet() {
Counter c = counterDao.findForUpdate(); // 悲觀鎖 SELECT FOR UPDATE
c.setCountValue(c.getCountValue() + 1);
counterDao.saveOrUpdate(c);
return c.getCountValue();
}
}
<context:component-scan base-package="com.counter"/>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.counter.model"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<%@ page import="com.counter.service.CounterService" %>
<%
CounterService svc = (CounterService) application.getAttribute("counterService");
if (svc == null) { // 簡易取得 Spring Bean,生產用 WebApplicationContextUtils
svc = (CounterService) new ClassPathXmlApplicationContext("applicationContext.xml").getBean("counterService");
application.setAttribute("counterService", svc);
}
long count = svc.incrementAndGet();
%>
當前訪問次數:<%= count %>
| 版本 | 持久化 | 併發控制 | 架構 | 適用場景 |
|---|---|---|---|---|
| 1. 第一版 | application 域 | 無 | Scriptlet | 單機測試、極低併發 |
| 2. 修正版 | application 域 | synchronized | Scriptlet | 單機、中低併發 |
| 3. 檔案版 | 本地檔案 | synchronized | Scriptlet | 單機、需重啟保留 |
| 4. JDBC 版 | MySQL | SELECT FOR UPDATE (行鎖) | Scriptlet + JNDI | 集群、強一致性 |
| 5. JavaBean 版 | application/檔案/DB | 封裝於 Bean | 分層 (POJO) | 教學、邏輯復用 |
| 6. Hibernate 版 | MySQL (ORM) | 悲觀鎖/樂觀鎖 + @Transactional | Spring + 分層 | 企業級生產環境 |
@Transactional 聲明式事務,避免手動 try-catch-commit/rollback。@Version 適合低衝突場景,高衝突改悲觀鎖 LockMode.PESSIMISTIC_WRITE。計數器教學系列完結,這套演進邏輯可直接映射到任何「讀-改-寫」熱點業務(庫存扣減、點讚計數、API 限流)的架構升級路徑。