首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 留言板項目:服務層接口的 Hibernate 實現詳解

     編輯:Java 相關知識

項目背景與架構定位

在典型的 Java Web 分層架構中,留言板系統的服務層 (Service Layer) 負責業務邏輯編排、事務控制與數據訪問協調。本文基於 Hibernate 實現服務層接口,展示從 DAO 到 Service 的完整鏈路,涵蓋實體映射、會話管理、事務邊界與異常處理。

核心實體映射設計

留言實體

@Entity
@Table(name = "message_board")
public class Message {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 50)
    private String author;
    
    @Column(nullable = false, length = 100)
    private String title;
    
    @Lob @Column(nullable = false)
    private String content;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "post_time", nullable = false, updatable = false)
    private Date postTime = new Date();
    
    @Column(name = "ip_address", length = 45)
    private String ipAddress;
    
    // Getters/Setters, equals/hashCode 基於 id
}

回覆實體 (可選擴展)

@Entity
@Table(name = "message_reply")
public class Reply {
    @Id @GeneratedValue
    private Long id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "message_id", nullable = false)
    private Message message;
    
    @Column(nullable = false, length = 50)
    private String author;
    
    @Lob @Column(nullable = false)
    private String content;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "reply_time", nullable = false)
    private Date replyTime = new Date();
}

DAO 接口與 Hibernate 實現

通用 DAO 接口

public interface GenericDao {
    T save(T entity);
    void delete(T entity);
    T getById(ID id);
    List findAll();
    List findByCriteria(DetachedCriteria criteria);
    long countByCriteria(DetachedCriteria criteria);
}

Hibernate 基礎實現

@Repository
public class HibernateGenericDao implements GenericDao {
    
    @Autowired private SessionFactory sessionFactory;
    private final Class entityClass;
    
    public HibernateGenericDao(Class entityClass) {
        this.entityClass = entityClass;
    }
    
    protected Session getCurrentSession() {
        return sessionFactory.getCurrentSession(); // 綁定到 Spring 事務
    }
    
    @Override
    public T save(T entity) {
        getCurrentSession().saveOrUpdate(entity);
        return entity;
    }
    
    @Override
    public void delete(T entity) {
        getCurrentSession().delete(entity);
    }
    
    @Override
    public T getById(ID id) {
        return getCurrentSession().get(entityClass, id);
    }
    
    @Override
    public List findAll() {
        return getCurrentSession().createCriteria(entityClass).list();
    }
    
    @Override
    public List findByCriteria(DetachedCriteria criteria) {
        return criteria.getExecutableCriteria(getCurrentSession()).list();
    }
    
    @Override
    public long countByCriteria(DetachedCriteria criteria) {
        Criteria executable = criteria.getExecutableCriteria(getCurrentSession());
        executable.setProjection(Projections.rowCount());
        return (Long) executable.uniqueResult();
    }
}

留言專用 DAO


public interface MessageDao extends GenericDao {
    List findLatest(int maxResults);
    List findByAuthor(String author, int offset, int limit);
    long countByAuthor(String author);
}

@Repository
public class HibernateMessageDao extends HibernateGenericDao implements MessageDao {
    public HibernateMessageDao() { super(Message.class); }
    
    @Override
    public List findLatest(int maxResults) {
        DetachedCriteria dc = DetachedCriteria.forClass(Message.class)
            .addOrder(Order.desc("postTime"));
        return findByCriteria(dc.setMaxResults(maxResults));
    }
    
    @Override
    public List findByAuthor(String author, int offset, int limit) {
        DetachedCriteria dc = DetachedCriteria.forClass(Message.class)
            .add(Restrictions.eq("author", author))
            .addOrder(Order.desc("postTime"));
        return findByCriteria(dc.setFirstResult(offset).setMaxResults(limit));
    }
    
    @Override
    public long countByAuthor(String author) {
        DetachedCriteria dc = DetachedCriteria.forClass(Message.class)
            .add(Restrictions.eq("author", author));
        return countByCriteria(dc);
    }
}

服務層接口設計


public interface MessageService {
    Message postMessage(String author, String title, String content, String ip);
    Message getMessage(Long id);
    List getLatestMessages(int count);
    PagedResult getMessagesByAuthor(String author, int page, int pageSize);
    void deleteMessage(Long id, String currentUser); // 權限檢查
    Reply addReply(Long messageId, String author, String content);
}

服務層實現:事務與異常處理


@Service
@Transactional(readOnly = true) // 默認只讀,寫操作方法覆蓋
public class MessageServiceImpl implements MessageService {
    
    @Autowired private MessageDao messageDao;
    @Autowired private GenericDao replyDao;
    
    @Override
    @Transactional(readOnly = false, rollbackFor = Exception.class)
    public Message postMessage(String author, String title, String content, String ip) {
        // 輸入驗證
        if (StringUtils.isAnyBlank(author, title, content)) {
            throw new IllegalArgumentException("作者、標題、內容均不能為空");
        }
        if (author.length() > 50 || title.length() > 100) {
            throw new IllegalArgumentException("作者或標題長度超限");
        }
        
        Message msg = new Message();
        msg.setAuthor(HtmlUtils.htmlEscape(author)); // 防 XSS
        msg.setTitle(HtmlUtils.htmlEscape(title));
        msg.setContent(HtmlUtils.htmlEscape(content));
        msg.setIpAddress(ip);
        
        messageDao.save(msg);
        return msg; // 返回持久化後帶 ID 的實體
    }
    
    @Override
    public Message getMessage(Long id) {
        return messageDao.getById(id);
    }
    
    @Override
    public List getLatestMessages(int count) {
        return messageDao.findLatest(Math.min(count, 100)); // 防濫用
    }
    
    @Override
    public PagedResult getMessagesByAuthor(String author, int page, int pageSize) {
        int safePage = Math.max(page, 1);
        int safeSize = Math.min(Math.max(pageSize, 1), 50);
        int offset = (safePage - 1) * safeSize;
        
        List data = messageDao.findByAuthor(author, offset, safeSize);
        long total = messageDao.countByAuthor(author);
        
        return new PagedResult<>(data, total, safePage, safeSize);
    }
    
    @Override
    @Transactional(readOnly = false)
    public void deleteMessage(Long id, String currentUser) {
        Message msg = messageDao.getById(id);
        if (msg == null) throw new EntityNotFoundException("留言不存在: " + id);
        
        // 權限檢查:作者本人或管理員
        if (!msg.getAuthor().equals(currentUser) && !"admin".equals(currentUser)) {
            throw new AccessDeniedException("無權刪除他人留言");
        }
        
        // 級聯刪除回覆 (或由資料庫外鍵 CASCADE 處理)
        messageDao.delete(msg);
    }
    
    @Override
    @Transactional(readOnly = false)
    public Reply addReply(Long messageId, String author, String content) {
        Message msg = messageDao.getById(messageId);
        if (msg == null) throw new EntityNotFoundException("留言不存在: " + messageId);
        
        Reply reply = new Reply();
        reply.setMessage(msg);
        reply.setAuthor(HtmlUtils.htmlEscape(author));
        reply.setContent(HtmlUtils.htmlEscape(content));
        
        replyDao.save(reply);
        return reply;
    }
}

關鍵技術點總結

領域最佳實踐
會話管理Spring OpenSessionInViewFilter 或 @Transactional 綁定 Session 到線程,DAO 用 getCurrentSession()
事務邊界服務層方法標註 @Transactional,讀操作 readOnly=true,寫操作顯式 readOnly=false + rollbackFor
異常轉換捕獲 Hibernate/JDBC 異常轉為業務異常 (DataAccessException 體系),不向上拋漏實現細節
防 XSS輸入端 HtmlUtils.htmlEscape(),輸出端 JSTL <c:out escapeXml="true"> 雙重保障
分頁查詢DAO 層封裝 DetachedCriteria + setFirstResult/setMaxResults,Service 組裝 PagedResult
延遲加載關聯默認 LAZY,Service 層按需初始化 (如 Hibernate.initialize(message.getReplies())) 或用 FETCH JOIN

配置要點 (Spring + Hibernate)

<!-- SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="packagesToScan" value="com.example.messageboard.entity"/>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
            <prop key="hibernate.show_sql">false</prop>
            <prop key="hibernate.format_sql">true</prop>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
        </props>
    </property>
</bean>

<!-- 事務管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

總結

本實現展示了領域驅動設計思想在留言板服務層的落地:實體豐富行為、DAO 抽象持久化細節、Service 封裝業務用例與事務邊界、Spring 聲明式事務消除樣板代碼。核心優勢在於:

  1. 分層清晰,單元測試友好 (DAO/Service 可獨立 Mock)
  2. 事務語義明確,異常自動回滾
  3. Hibernate 原生 API 與 Spring 無縫整合,無侵入式代碼
  4. 擴展性強:添加回覆、點贊、舉報等功能只需新增 Entity/DAO/Service 方法
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved