本系列最終篇,展示如何將 Hibernate ORM 引入留言板專案,替換手寫 JDBC 實現,大幅簡化 DAO/Service 代碼,並說明頁面層面的微調。

<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/msgboard?useSSL=false</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123456</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.current_session_context_class">thread</property>
<mapping class="com.example.model.Message"/>
<mapping class="com.example.model.User"/>
</session-factory>
</hibernate-configuration>
@Entity @Table(name = "message_board")
public class Message {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "parent_id") private Integer parentId;
@Column(name = "user_id") private Integer userId;
private String title;
@Lob private String content;
@Column(name = "post_time") private Date postTime;
private Integer status;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", insertable = false, updatable = false)
private User user;
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@OrderBy("postTime ASC")
private List<Message> replies;
@Transient private Integer replyCount; // 非持久化屬性
// getter/setter 略
}
@Entity @Table(name = "users")
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
private String password;
private String email;
@Column(name = "reg_time") private Date regTime;
// getter/setter 略
}
@Repository
public class MessageDao {
@Autowired private SessionFactory sessionFactory;
private Session getSession() { return sessionFactory.getCurrentSession(); }
public void save(Message m) { getSession().save(m); }
public void update(Message m) { getSession().update(m); }
public void deleteById(Integer id) {
Message m = getSession().get(Message.class, id);
if (m != null) m.setStatus(0); // 軟刪除
}
public Message findById(Integer id) { return getSession().get(Message.class, id); }
@SuppressWarnings("unchecked")
public List<Message> findTopics(int offset, int limit) {
return getSession().createQuery(
"FROM Message WHERE parentId = 0 AND status = 1 ORDER BY postTime DESC")
.setFirstResult(offset).setMaxResults(limit).list();
}
public long countTopics() {
return (Long) getSession().createQuery(
"SELECT COUNT(*) FROM Message WHERE parentId = 0 AND status = 1").uniqueResult();
}
}
@Service @Transactional
public class MessageServiceImpl implements MessageService {
@Autowired private MessageDao messageDao;
@Autowired private UserDao userDao;
@Override
public void postTopic(Integer userId, String title, String content) {
Message m = new Message();
m.setParentId(0); m.setUserId(userId); m.setTitle(title); m.setContent(content);
m.setPostTime(new Date()); m.setStatus(1);
messageDao.save(m);
}
@Override
public PageBean<Message> findTopics(int pageNo, int pageSize) {
long total = messageDao.countTopics();
List<Message> list = messageDao.findTopics((pageNo-1)*pageSize, pageSize);
// 回覆數可用 @Formula 或子查詢優化,此處簡單遍歷
for (Message m : list) m.setReplyCount(countDirectReplies(m.getId()));
return new PageBean<>(pageNo, pageSize, total, list);
}
// 其他方法同理極簡...
}
PageBean<Message>、Message、List<Message>,視圖層完全不知後端已換 ORM。Message 加 @Formula("(SELECT COUNT(*) FROM message_board WHERE parent_id = id AND status = 1)") 由 Hibernate 自動查詢,避免 N+1 問題。setFirstResult/setMaxResults 由 Hibernate 翻譯為資料庫原生 LIMIT,效能等同手寫 SQL。| 維度 | 手寫 JDBC | Hibernate |
|---|---|---|
| 代碼量 | 大(CRUD 模板、映射、資源管理) | 極少(註解+幾行 HQL) |
| SQL 控制力 | 完全掌控 | 可通過 HQL/Native SQL 微調 |
| 學習曲線 | 低 | 中(需懂 Session、事務、延遲加載) |
| 效能調優 | 手動索引、SQL 調優 | 二級快取、批次抓取、@Formula、延遲加載策略 |
| 遷移成本 | 無 | 需重寫 DAO/實體,Service/Controller 不動 |
十篇教學完整走過:
核心最佳實踐:介面隔離實現、JSTL/EL 消除 Scriptlet、參數化 SQL 防注入、分頁下推資料庫、軟刪除保留歷史、CSRF/XSS 防護、事務註解管理。這套架構可直接作為中小型 Java Web 專案的起始模板。