2016-08-29 7 views
12

У меня есть класс Client. Я хочу иметь возможность проверять изменения свойств этого класса (не весь класс - просто его свойства).Аудит Изменение свойств - Spring MVC + JPA

public class Client { 
private Long id; 
private String firstName; 
private String lastName; 
private String email; 
private String mobileNumber; 
private Branch companyBranch; 

На самом деле это очень легко проверить всю сущность с помощью @Audited аннотации.

Но я хочу проверить эти изменения, используя мою структуру классов.

вот мой желаемый класс результата:

public class Action { 
private String fieldName; 
private String oldValue; 
private String newValue; 
private String action; 
private Long modifiedBy; 
private Date changeDate; 
private Long clientID; 

результат должен выглядеть следующим образом:

FIELDNAME + "был изменен с" + OldValue + "к" + новое_значению + "для" ClientId + "by" modifiedBy;

  • mobileNumber был изменен с 555 на 999 для Bill Gates by George.

Причина, по которой я делаю это, заключается в том, что мне нужно сохранить эти изменения в БД под таблицей действий - потому что я буду проверять свойства из разных объектов, и я хочу сохранить их вместе, а затем получить возможность получить когда мне нужно.

Как это сделать?

Благодаря

+1

JPA не предоставляет способ проведения аудита изменений определенных свойств. Если вы используете Hibernate в качестве поставщика JPA, вы можете написать свой собственный [Interceptor] (https://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/Interceptor.html), реализовать 'onFlushDirty ', проверьте поля, чтобы найти, какие из них были изменены, и затем создайте журнал аудита. – manish

+1

Можете ли вы привести пример использования перехватчика? Я использую Hibernate в качестве провайдера JPA. – Irakli

+1

См. [Официальная документация] (http://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#events). – manish

ответ

8

Aop - это правильный путь. Вы можете использовать AspectJ с полем set() pointcut для ваших нужд. С аспектом before вы можете извлечь необходимую информацию для заполнения объекта Action.

Также вы можете использовать пользовательский класс Annotation @AopAudit для определения классов, которые вы хотите проверить. Вы должны определить такую ​​аннотацию в своем пути к классам и разместить ее под целевыми классами, которые вы хотите провести аудит.

Этот подход может выглядеть следующим образом:

AopAudit.Java

@Retention(RUNTIME) 
@Target(TYPE) 
public @interface AopAudit { 

} 

Client.java

@AopAudit 
public class Client { 
    private Long id; 
    private String firstName; 
    private String lastName; 
    private String email; 
    private String mobileNumber; 
} 

AuditAnnotationAspect.aj

import org.aspectj.lang.reflect.FieldSignature; 

import java.lang.reflect.Field; 

public aspect FieldAuditAspect { 

pointcut auditField(Object t, Object value): set(@(*.AopAudit) * *.*) && args(value) && target(t); 

pointcut auditType(Object t, Object value): set(* @(*.AopAudit) *.*) && args(value) && target(t); 

before(Object target, Object newValue): auditField(target, newValue) || auditType(target, newValue) { 
     FieldSignature sig = (FieldSignature) thisJoinPoint.getSignature(); 
     Field field = sig.getField(); 
     field.setAccessible(true); 

     Object oldValue; 
     try 
     { 
      oldValue = field.get(target); 
     } 
     catch (IllegalAccessException e) 
     { 
      throw new RuntimeException("Failed to create audit Action", e); 
     } 

     Action a = new Action(); 
     a.setFieldName(sig.getName()); 
     a.setOldValue(oldValue == null ? null : oldValue.toString()); 
     a.setNewValue(newValue == null ? null : newValue.toString()); 
    } 

} 

Это AspectJ аспект, который определяют auditField Pointcut захватить набор полевых операций и before журнал ic для создания объекта Audit.

Чтобы включить AspectJ Compile Time Weaving вы должны сделать следующее в случае Maven:

pom.xml

... 

<dependencies> 
    <dependency> 
     <groupId>org.aspectj</groupId> 
     <artifactId>aspectjrt</artifactId> 
    </dependency> 
</dependencies> 

... 

<plugins> 
    <plugin> 
     <groupId>org.codehaus.mojo</groupId> 
     <artifactId>aspectj-maven-plugin</artifactId> 
     <version>1.6</version> 
     <configuration> 
      <showWeaveInfo>true</showWeaveInfo> 
      <source>${java.source}</source> 
      <target>${java.target}</target> 
      <complianceLevel>${java.target}</complianceLevel> 
      <encoding>UTF-8</encoding> 
      <verbose>false</verbose> 
      <XnoInline>false</XnoInline> 
     </configuration> 
     <executions> 
      <execution> 
       <id>aspectj-compile</id> 
       <goals> 
        <goal>compile</goal> 
       </goals> 
      </execution> 
      <execution> 
       <id>aspectj-compile-test</id> 
       <goals> 
        <goal>test-compile</goal> 
       </goals> 
      </execution> 
     </executions> 
     <dependencies> 
      <dependency> 
       <groupId>org.aspectj</groupId> 
       <artifactId>aspectjrt</artifactId> 
       <version>${aspectj.version}</version> 
      </dependency> 
      <dependency> 
       <groupId>org.aspectj</groupId> 
       <artifactId>aspectjtools</artifactId> 
       <version>${aspectj.version}</version> 
      </dependency> 
     </dependencies> 
    </plugin> 
</plugins> 

Эта Maven конфигурация позволяет AspectJ компилятор, который делает обработку байткод после ваших занятий.

applicationContext.xml

<bean class="AuditAnnotationAspect" factory-method="aspectOf"/> 

Также вы можете добавить экземпляр аспект в контексте Spring приложения для инъекции зависимостей.

UPD: Here является примером конфигурации таких проектов AspectJ

+1

Это правильный способ ответить на вопросы! –

+1

@JarrodRoberson, если вы занижаете все плохие ответы, тогда будет справедливо, чтобы поддержать хорошие ответы. –

+1

Я прокомментировал качество попытки, я не уверен, что это действительно полезно или правильно, но это лучше, чем комментарий одного лайнера в качестве ответов, которые вы делали, я пытаюсь дать вам некоторые преимущества для улучшения. Все, что сказал, это ** слишком широкий вопрос, который в основном ** присылайте мне кодz **, который является наихудшим вопросом. Это также нарушает фол ** вне темы: рекомендации **, поэтому я, как правило, проголосую за все ответы, чтобы не дать кому-либо ответить на подобные вещи. Единственная причина, по которой это все еще открыта, - это щедрость. –

3

Если вы используете спящий режим, вы можете использовать Hibernate Envers, и определить свой собственный RevisionEntity (Если вы хотите работать с java.time вы будете нуждаться в Hibernate 5.x. В более ранних версиях даже пользовательские JSR-310 утилит не будет работать для целей аудита)

Если вы не используете Hibernate или хотите иметь чистое решение JPA, вам нужно будет написать свое собственное решение с использованием механизма JPA EntityListeners.

1

Я не знаю точно, что «ModifiedBy» атрибут (пользователь приложения или другого Клиента?), Но игнорируя этот, вы можете поймать изменения всех атрибутов в инкубаторе

(Примечание: изменение реализации сеттера или добавление параметров других в инкубатор являются плохая практике этой работа должна быть выполнена с использованием регистратора или АОП):

public class Client { 
    private Long id; 
    private String firstName; 
    private String lastName; 
    private String email; 
    private String mobileNumber; 
    private Branch companyBranch; 
    @OneToMany(cascade = CascadeType.ALL) 
    @JoinColumn("client_ID"); 
    List<Action> actions = new ArrayList<String>(); 

    public void setFirstName(String firstName,Long modifiedBy){ 
    // constructor  Action(fieldName, oldValue,  newValue ,modifiedBy) 
    this.actions.add(new Action("firstName",this.firstName,firstName,modifiedBy)); 
    this.firstName=firstName; 
    } 
//the same work for lastName,email,mobileNumber,companyBranch 
} 

Примечания: лучше и правильное решение заключается в использовании регистратора или AOP

+1

Можете ли вы дать мне пример для АОП? – Irakli

+1

см. Эту ссылку http://five.agency/logging-with-spring-aop/ –

+0

это мне не помогло. Можете ли вы предложить что-нибудь еще? – Irakli

1

АОП absulately решение для Вашего случая, я реализовал подобный случай с Spring AOP, чтобы сохранить сущности изменения. Для этого решения необходимо использовать около pointcut.

Другим решением является использование org.hibernate.Interceptor, то org.hibernate.EmptyInterceptor должно быть соответствующее расширение, я пишу некоторые простые коды для его имитации (ваши коды клиента):

@Entity 
public class Client { 
    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    private Long id; 
    private String firstName; 
    private String lastName; 
    private String email; 
    private String mobileNumber; 
    // getter and setter 
} 

Interceptor реализация

public class StateChangeInterceptor extends EmptyInterceptor { 
    @Override 
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) { 
     if (entity instanceof Client) { 
      for (int i = 0; i < propertyNames.length; i++) { 
       if (currentState[i] == null && previousState[i] == null) { 
        return false; 
       } else { 
        if (!currentState[i].equals(previousState[i])) { 
         System.out.println(propertyNames[i] + " was changed from " + previousState[i] + " to " + currentState[i] + " for " + id); 
        } 
       } 

      } 
     } 

     return true; 
    } 

    @Override 
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { 
     return super.onSave(entity, id, state, propertyNames, types); 
    } 
} 

Зарегистрируйте насадок, я использую весенний ботинок, поэтому просто добавьте его в application.properties

spring.jpa.properties.hibernate.ejb.interceptor=io.cloudhuang.jpa.StateChangeInterceptor 

Вот тест

@Test 
public void testStateChange() { 
    Client client = new Client(); 
    client.setFirstName("Liping"); 
    client.setLastName("Huang"); 

    entityManager.persist(client); 
    entityManager.flush(); 

    client.setEmail("[email protected]"); 
    entityManager.persist(client); 
    entityManager.flush(); 
} 

будет получить выход как:

email was changed from null to [email protected] for 1 

Поэтому предположим, что можно заменить Action объектов.

А вот проект с открытым исходным кодом JaVers - object auditing and diff framework for Java

JaVers является легкой библиотекой Java для изменения аудита в ваших данных.

Вы можете ознакомиться с данным проектом.

+1

Насколько Interceptor повлияет на производительность приложения? – Irakli

+1

@JONIVAR Извините, я не сделал Benchmark для этого, и на самом деле это зависит от конкретной реализации перехватчика. –

+1

@ JONIVar это может вам помочь? –

1

Я бы предпочел, чтобы вы переопределили метод equals вашего объекта с помощью свойства Audit. И в DAO вы просто сравниваете старый экземпляр экземпляра с новым экземпляром, используя метод equals, который вы создали внутри объекта.

Вы сможете узнать, является ли это аудитом или нет.

 Смежные вопросы

  • Нет связанных вопросов^_^