Hibernate 拦截器Interceptor使用

本文介绍了一种使用Hibernate拦截器在增删改操作时自动记录日志的方法,通过创建日志表、实体类接口和实现类,实现了对数据操作的日志记录,避免了使用触发器带来的复杂性和数据库特性依赖问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如果想在DAO层对插入,更新,读取数据进行过滤更改,可以使用EmptyInterceptor。用法如下:

public class VideoServerInterceptor extends EmptyInterceptor {

    /**
     *
     */
    private static final long serialVersionUID = 6314774135018183911L;

    public boolean onFlushDirty(
            Object entity,
            Serializable id,
            Object[] state,
            Object[] previousState,
            String[] propertyNames,
            Type[] types) {
        if (entity instanceof VideoServer) {
            boolean modified = false;
            for (int i = 0; i < propertyNames.length; i++) {
                if ("adslPassword".equals(propertyNames[i])) {
                    if (null != state[i]) {
                        modified = true;
                        state[i] = VissEncode.vssEncrypt(state[i].toString());
                    }
                } else if ("tr069Password".equals(propertyNames[i])) {
                    if (null != state[i]) {
                        modified = true;
                        state[i] = VissEncode.vssEncrypt(state[i].toString());
                    }
                } else if ("loginPassword".equals(propertyNames[i])) {
                    if (null != state[i]) {
                        modified = true;
                        state[i] = VissEncode.vssEncrypt(state[i].toString());
                    }
                }
            }

            return modified;
        }
        return false;
    }
    
    @Override
    public boolean onLoad(Object entity, Serializable id, Object[] state,
            String[] propertyNames, Type[] types) {
        if (entity instanceof VideoServer) {
            boolean modified = false;
            for (int i = 0; i < propertyNames.length; i++) {
                if ("adslPassword".equals(propertyNames[i])) {
                    if (null != state[i]) {
                        modified = true;
                        state[i] = VissEncode.vssDecrypt(state[i].toString());
                    }
                } else if ("tr069Password".equals(propertyNames[i])) {
                    if (null != state[i]) {
                        modified = true;
                        state[i] = VissEncode.vssDecrypt(state[i].toString());
                    }
                } else if ("loginPassword".equals(propertyNames[i])) {
                    if (null != state[i]) {
                        modified = true;
                        state[i] = VissEncode.vssDecrypt(state[i].toString());
                    }
                }
            }

            return modified;
        }
        return false;
    }

    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state,
            String[] propertyNames, Type[] types) {
        if (entity instanceof VideoServer) {
            boolean modified = false;
            for (int i = 0; i < propertyNames.length; i++) {
                if ("adslPassword".equals(propertyNames[i])) {
                    if (null != state[i]) {
                        modified = true;
                        state[i] = VissEncode.vssEncrypt(state[i].toString());
                    }
                } else if ("tr069Password".equals(propertyNames[i])) {
                    if (null != state[i]) {
                        modified = true;
                        state[i] = VissEncode.vssEncrypt(state[i].toString());
                    }
                } else if ("loginPassword".equals(propertyNames[i])) {
                    if (null != state[i]) {
                        modified = true;
                        state[i] = VissEncode.vssEncrypt(state[i].toString());
                    }
                }
            }

            return modified;
        }
        return false;
    }
}


这样就实现了对属性的更改,当然也可以作为实体类的日志记录。。。这里转一下其他文章转自http://www.yihaomen.com/article/java/464.htm

===============================================================================================

开发应用程序的过程中,经常会对一些比较重要的数据修改都需要写日志。在实际工作的工程中,这些数据都是存在表中的, 一个常见的做法是用触发器,在增删改的时候,用触发器将数据写入到另一张表中去,但个人不推荐这么做,原因如下:
1. 如果有多个表,得写很多触发器。
2. 触发器与数据库特性关联太紧,不同的数据库,虽然思路一样,但语法却不太一样。
对数据库表操作的日志记录,完全可以利用Hibernate的Interceptor特性来实现,也就是拦截器。下面用一个具体的例子来说明如何使用Hibernate的Interceptor。

创建一个表,用来记录日志的表

程序代码 程序代码

Create TABLE  `auditlog` (
  `AUDIT_LOG_ID` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `ACTION` VARCHAR(100) NOT NULL,
  `DETAIL` text NOT NULL,
  `CreateD_DATE` DATE NOT NULL,
  `ENTITY_ID` BIGINT(20) UNSIGNED NOT NULL,
  `ENTITY_NAME` VARCHAR(255) NOT NULL,
  PRIMARY KEY (`AUDIT_LOG_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;


创建这个表对应的实体类:
程序代码 程序代码


@Entity
@Table(name = "auditlog")
public class AuditLog implements java.io.Serializable {

    private Long auditLogId;
    private String action;
    private String detail;
    private Date createdDate;
    private long entityId;
    private String entityName;

    public AuditLog() {
    }

    public AuditLog(String action, String detail, Date createdDate,
            long entityId, String entityName) {
        this.action = action;
        this.detail = detail;
        this.createdDate = createdDate;
        this.entityId = entityId;
        this.entityName = entityName;
    }

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "AUDIT_LOG_ID", unique = true, nullable = false)
    public Long getAuditLogId() {
        return this.auditLogId;
    }
        .... 余下部分可以参考提供下载的源代码.


创建一个接口,所有实现了这个接口的实体类,都会写日志
程序代码 程序代码

package com.mkyong.interceptor;

//market interface
public interface IAuditLog {    
    public Long getId();    
    public String getLogDeatil();
}

这里有两个方法,getId,getLogDetail 需要实现类去实现具体的方法,也就是要被写入到日志表中的详细记录.

创建一个类实现了IAuditLog 接口,并给出接口方法的具体实现
程序代码 程序代码

@Entity
@Table(name="stock")
public class Stock implements java.io.Serializable,IAuditLog  {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="STOCK_ID")
    private Integer stockId;
    
    @Column(name="STOCK_CODE", length=10)
    private String stockCode;
    
    @Column(name="STOCK_NAME", length=20)
    private String stockName;

    public Stock() {
    }

    public Stock(String stockCode, String stockName) {
        this.stockCode = stockCode;
        this.stockName = stockName;
    }

    ....省略部分getter,setter。

    @Transient
    public Long getId(){
        return this.stockId.longValue();
    }
    
    @Transient
    public String getLogDeatil(){
        StringBuilder sb = new StringBuilder();
        sb.append(" Stock Id : ").append(stockId)
        .append(" Stock Code : ").append(stockCode)
        .append(" Stock Name : ").append(stockName);

        return sb.toString();
    }

}


创建记录日志的工具类,所有写日志公用
程序代码 程序代码

public class AuditLogUtil{
    
    public static void LogIt(String action,
        IAuditLog entity){
        
        Session tempSession = HibernateUtil.getSessionFactory().openSession();
            
        try {
            tempSession.getTransaction().begin();
            AuditLog auditRecord = new AuditLog(action,entity.getLogDeatil()
                    , new Date(),entity.getId(), entity.getClass().toString());
            tempSession.save(auditRecord);
            tempSession.getTransaction().commit();
            
        } finally {    
            tempSession.close();
            
        }
            
    }
}


创建 Hibernate interceptor 拦截器,这是重点,这里拦截所有需要记录日志的类,并处理
程序代码 程序代码

public class AuditLogInterceptor extends EmptyInterceptor{
    
    Session session;
    private Set inserts = new HashSet();
    private Set updates = new HashSet();
    private Set deletes = new HashSet();
    
    public void setSession(Session session) {
        this.session=session;
    }
        
    @Override
    public String onPrepareStatement(String sql) {
        System.out.println("execute sql: " + sql);
        return super.onPrepareStatement(sql);
    }

    public boolean onSave(Object entity,Serializable id,
        Object[] state,String[] propertyNames,Type[] types)
        throws CallbackException {
        
        System.out.println("onSave");
        
        if (entity instanceof IAuditLog){
            inserts.add(entity);
        }
        return false;
            
    }
    
    public boolean onFlushDirty(Object entity,Serializable id,
        Object[] currentState,Object[] previousState,
        String[] propertyNames,Type[] types)
        throws CallbackException {
    
        System.out.println("onFlushDirty");
        
        if (entity instanceof IAuditLog){
            updates.add(entity);
        }
        return false;
        
    }
    
    public void onDelete(Object entity, Serializable id,
        Object[] state, String[] propertyNames,
        Type[] types) {
        
        System.out.println("onDelete");
        
        if (entity instanceof IAuditLog){
            deletes.add(entity);
        }
    }

    //called before commit into database
    public void preFlush(Iterator iterator) {
        System.out.println("preFlush");
    }    
    
    //called after committed into database
    public void postFlush(Iterator iterator) {
        System.out.println("postFlush");
        
        try{
        
            for (Iterator it = inserts.iterator(); it.hasNext();) {
                IAuditLog entity = (IAuditLog) it.next();
                System.out.println("postFlush - insert");
                
                AuditLogUtil.LogIt("Saved",entity);
            }    
            
            for (Iterator it = updates.iterator(); it.hasNext();) {
                IAuditLog entity = (IAuditLog) it.next();
                System.out.println("postFlush - update");
                AuditLogUtil.LogIt("Updated",entity);
            }    
            
            for (Iterator it = deletes.iterator(); it.hasNext();) {
                IAuditLog entity = (IAuditLog) it.next();
                System.out.println("postFlush - delete");
                AuditLogUtil.LogIt("Deleted",entity);
            }    
            
        } finally {
            inserts.clear();
            updates.clear();
            deletes.clear();
        }
    }    
    
}

这里面有几个比较常用的方法:
onSave – 保存数据的时候调用,数据还没有保存到数据库.
onFlushDirty – 更新数据时调用,但数据还没有更新到数据库
onDelete – 删除时调用.
preFlush – 保存,删除,更新 在提交之前调用 (通常在 postFlush 之前).
postFlush – 提交之后调用(commit之后)

写测试例子, 添加数据,更新数据,然后再删掉
程序代码 程序代码

public class App {
    public static void main(String[] args) {

        Session session = null;
        Transaction tx = null;

        try {

            AuditLogInterceptor interceptor = new AuditLogInterceptor();
            
            session = HibernateUtil.getSessionFactory().withOptions().interceptor(interceptor).openSession();
            //session = HibernateUtil.getSessionFactory().openSession();
            //interceptor.setSession(session);
            
            //test insert
            tx = session.beginTransaction();
            Stock stockInsert = new Stock();
            stockInsert.setStockCode("1111");
            stockInsert.setStockName("yihaomen");
            session.saveOrUpdate(stockInsert);
            tx.commit();
            
            //test update
            tx = session.beginTransaction();
            Query query = session.createQuery("from Stock where stockCode = '1111'");
            Stock stockUpdate = (Stock)query.list().get(0);
            stockUpdate.setStockName("yihaomen-update");
            session.saveOrUpdate(stockUpdate);
            tx.commit();
            
            //test delete
            tx = session.beginTransaction();
            session.delete(stockUpdate);
            tx.commit();

        } catch (RuntimeException e) {
            try {
                tx.rollback();
            } catch (RuntimeException rbe) {
                // log.error("Couldn抰 roll back transaction", rbe);
            }
            throw e;
        } finally {
            if (session != null) {
                session.close();
            }
        }

    }

}


运行结果如下:
程序代码 程序代码

onSave
execute sql: insert into stock (STOCK_CODE, STOCK_NAME) values (?, ?)
Hibernate: insert into stock (STOCK_CODE, STOCK_NAME) values (?, ?)
preFlush
postFlush
postFlush - insert
Hibernate: insert into auditlog (ACTION, CreateD_DATE, DETAIL, ENTITY_ID, ENTITY_NAME) values (?, ?, ?, ?, ?)
preFlush
execute sql: select stock0_.STOCK_ID as STOCK_ID1_1_, stock0_.STOCK_CODE as STOCK_CO2_1_, stock0_.STOCK_NAME as STOCK_NA3_1_ from stock stock0_ where stock0_.STOCK_CODE='1111'
Hibernate: select stock0_.STOCK_ID as STOCK_ID1_1_, stock0_.STOCK_CODE as STOCK_CO2_1_, stock0_.STOCK_NAME as STOCK_NA3_1_ from stock stock0_ where stock0_.STOCK_CODE='1111'
preFlush
onFlushDirty
execute sql: update stock set STOCK_CODE=?, STOCK_NAME=? where STOCK_ID=?
Hibernate: update stock set STOCK_CODE=?, STOCK_NAME=? where STOCK_ID=?
postFlush
postFlush - update
Hibernate: insert into auditlog (ACTION, CreateD_DATE, DETAIL, ENTITY_ID, ENTITY_NAME) values (?, ?, ?, ?, ?)
onDelete
preFlush
execute sql: delete from stock where STOCK_ID=?
Hibernate: delete from stock where STOCK_ID=?
postFlush
postFlush - delete
Hibernate: insert into auditlog (ACTION, CreateD_DATE, DETAIL, ENTITY_ID, ENTITY_NAME) values (?, ?, ?, ?, ?)


另外查看 auditLog 这张表, 可以看到日志成功写入


另外,如果是在SPRING 容器中使用,应该将这个interceptor 注入进去
程序代码 程序代码

<bean id="hibermateInterceptor" class="com.yihaomen.interceptor.AuditLogInterceptor"/>  
      
    <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">  
        <property name="dataSource">  
            <ref local="dataSource_jdbc"/>  
        </property>  
        <property name="entityInterceptor" ref="hibermateInterceptor"/>
        ..............
   </bean>


这样就能实现对整个项目中需要记录日志的实体类进行拦截,并记录增删改的日志记录. 还是很方便的,重点就是 Hibernate interceptor 的使用.

测试在是在 Hibernate 4.3 下测试的, 如果是hibernate 3 在openSession的时候是不同的,hibernate4用了session = HibernateUtil.getSessionFactory().withOptions().interceptor(interceptor).openSession(); 如果是Hibernate 3的话,应该是:session = HibernateUtil.getSessionFactory().openSession(interceptor);。

### 整合 Hibernate 拦截器到 Spring Boot 的方法 在 Spring Boot 中整合 Hibernate 拦截器可以用于实现自定义逻辑或审计功能。以下是具体的方法: #### 配置 `LocalSessionFactoryBean` 或 `EntityManagerFactory` 由于 Spring Boot 自动配置了 Hibernate,因此可以通过设置 `hibernate.session_factory.interceptor` 属性来指定拦截器类。 ```properties spring.jpa.properties.hibernate.session_factory.interceptor=com.example.CustomInterceptor ``` 此属性会告诉 Hibernate 使用哪个拦截器实例[^1]。 #### 创建自定义拦截器类 创建一个继承自 `org.hibernate.EmptyInterceptor` 的类,并重写所需的方法。例如: ```java import org.hibernate.EmptyInterceptor; import java.io.Serializable; public class CustomInterceptor extends EmptyInterceptor { @Override public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { // 实现保存前的逻辑 System.out.println("Entity is being saved: " + entity); return super.onSave(entity, id, state, propertyNames, types); } @Override public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { // 实现在删除之前的逻辑 System.out.println("Entity is being deleted: " + entity); super.onDelete(entity, id, state, propertyNames, types); } } ``` 上述代码展示了如何通过覆盖 `onSave` 和 `onDelete` 方法,在实体保存和删除之前执行特定操作。 #### 注册拦截器到 Spring 应用上下文中 如果需要动态注册拦截器,则可以在应用启动时将其注入到 Bean 定义中。例如: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, DataSource dataSource) { Map<String, Object> properties = new HashMap<>(); properties.put("hibernate.session_factory.interceptor", customInterceptor()); return builder.dataSource(dataSource).packages("com.example").persistenceUnit("unitName") .properties(properties).build(); } @Bean public CustomInterceptor customInterceptor() { return new CustomInterceptor(); } } ``` 这段代码显示了如何将拦截器作为 Bean 进行管理并传递给 `entityManagerFactory` 的配置过程[^2]。 #### 测试集成效果 完成以上步骤后,运行应用程序并通过调试验证拦截器是否按预期工作。每次触发数据库交互时,相应的拦截器方法会被调用。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值