|
提到審計,首先從腦海中蹦出的就是審計日志,記錄了實體版本的修改信息。但是實現(xiàn)審計日志是個既耗時又復雜的任務(wù),幸運的是,大部分時候我們都不需要
然而,還是有些經(jīng)常碰到的問題:
* 實體什么時候被創(chuàng)建和修改?
* 誰創(chuàng)建和修改了這個實體?
Spring Data JPA的審計功能可以幫助我們解決這兩個問題,下面我們介紹一下怎樣通過Spring Data JPA提供的審計功能記錄實體的創(chuàng)建和修改時間
一開始,我們需要先寫一個返回當前日期和時間的服務(wù),定義一個DateTimeService接口
為什么使用接口而不是類實現(xiàn)這個服務(wù)呢,原因如下:
- 我們想創(chuàng)建兩個不同的實現(xiàn)類
- 第一個實現(xiàn)返回當前的日期和時間
- 第二個實現(xiàn)用于測試,每次返回相同的日期和時間
- 如果是個產(chǎn)品應用,其他組件也會通過接口進行調(diào)用
DateTimeService接口的聲明只有一個方法:
getCurrentDateAndTime(),該方法返回一個ZonedDateTime對象
DateTimeService接口代碼如下:
import java.time.ZonedDateTime;
public interface DateTimeService {
ZonedDateTime getCurrentDateAndTime();
}
CurrentTimeDateTimeService實現(xiàn)了DateTimeService接口,getCurrentDateAndTime()的實現(xiàn)只是簡單的返回了當前時間
CurrentTimeDateTimeService實現(xiàn)如下:
import java.time.ZonedDateTime;
public class CurrentTimeDateTimeService implements DateTimeService {
@Override
public ZonedDateTime getCurrentDateAndTime() {
return ZonedDateTime.now();
}
}
下面我們看看怎樣將我們寫的service與Spring Data JPA進行集成
Spring Data JPA使用DateTimeProvider接口獲取日期和時間,所以我們只要實現(xiàn)這個接口就可以將我們的service集成到Spring中
步驟如下:
- 創(chuàng)建一個
AuditingDateTimeProvider類,實現(xiàn)DateTimeProvider接口
- 添加一個
DateTimeService類型的字段,通過構(gòu)造函數(shù)注入
- 實現(xiàn)
getNow()方法,通過DateTimeService對象獲取當前日期和時間,返回一個GregorianCalendar對象
AuditingDateTimeProvider實現(xiàn)如下:
import org.springframework.data.auditing.DateTimeProvider;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class AuditingDateTimeProvider implements DateTimeProvider {
private final DateTimeService dateTimeService;
public AuditingDateTimeProvider(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
@Override
public Calendar getNow() {
return GregorianCalendar.from(dateTimeService.getCurrentDateAndTime());
}
}
下一步是對ApplicationContext進行配置,先創(chuàng)建一個DateTimeService類型的bean,在configuration class(XML configuration file)中聲明。通過如下步驟配置bean:
- 創(chuàng)建
currentTimeDateTimeService()方法返回一個CurrentTimeDateTimeService對象
- 加上
@Bean注解
- 加上
@Profile注解,值設(shè)置為Profiles.APPLICATION,確保應用啟動時bean被創(chuàng)建
相應的配置類如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
@Configuration
@ComponentScan("net.petrikainulainen.springdata.jpa")
@Import({WebMvcContext.class, PersistenceContext.class})
public class ExampleApplicationContext {
@Profile(Profiles.APPLICATION)
@Bean
DateTimeService currentTimeDateTimeService() {
return new CurrentTimeDateTimeService();
}
}
然后,將我們的DateTimeProvider bean配置到Spring Data JPA,通過更改事例程序的持久化層配置來實現(xiàn):
- 創(chuàng)建
dateTimeProvider()方法返回一個DateTimeProvider對象,構(gòu)造函數(shù)傳入我們的DateTimeService對象
- 返回一個
AuditingAwareDateTimeProvider對象
- 加上
@Bean注解
- 加上
@EnableJpaAuditing注解,dataTimeProviderRef的值設(shè)置為dateTimeProvider
相應的PersistenceContext配置如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider")
@EnableJpaRepositories(basePackages = {
"net.petrikainulainen.springdata.jpa.todo"
})
@EnableTransactionManagement
class PersistenceContext {
@Bean
DateTimeProvider dateTimeProvider(DateTimeService dateTimeService) {
return new AuditingDateTimeProvider(dateTimeService);
}
}
下面修改實體類,要實現(xiàn)如下需求:
- 確保creationTime在第一次存儲時被設(shè)置
- 確保modificationTime在第一次存儲和每次更新時被設(shè)置
通過下面步驟就能實現(xiàn)上述目標:
- 為creationTime字段加上
@CreatedDate注解
- 為modificationTime字段加上
@LastModifiedDate注解
- 為實體類加上
@EntityListeners注解,將值設(shè)置為AuditingEntityListener.class
相應的Todo實體類修改如下:
import org.hibernate.annotations.Type;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
import java.time.ZonedDateTime;
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "todos")
final class Todo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "creation_time", nullable = false)
@Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
@CreatedDate
private ZonedDateTime creationTime;
@Column(name = "description", length = 500)
private String description;
@Column(name = "modification_time")
@Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
@LastModifiedDate
private ZonedDateTime modificationTime;
@Column(name = "title", nullable = false, length = 100)
private String title;
@Version
private long version;
}
|