Вступление
В моей предыдущей статье я описал механизм автоматической проверки грязи Hibernate. Несмотря на то, что вы всегда должны предпочитать это, могут быть ситуации, когда вы захотите добавить свою собственную стратегию обнаружения загрязнения.
Пользовательские грязные стратегии проверки
Hibernate предлагает следующие механизмы настройки:
Ручная грязная проверка
В качестве упражнения я создам ручной механизм грязной проверки, чтобы показать, насколько легко вы можете настроить стратегию обнаружения изменений:
Сам грязная проверка сущности
Во-первых, я определю интерфейс DirtyAware, который должны быть реализованы всеми объектами, выполняющими ручную проверку:
1
2
3
4
5
6
|
public interface DirtyAware { Set<String> getDirtyProperties(); void clearDirtyProperties(); } |
Далее я собираюсь инкапсулировать нашу текущую грязную логику проверки в базовый класс:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public abstract class SelfDirtyCheckingEntity implements DirtyAware { private final Map<String, String> setterToPropertyMap = new HashMap<String, String>(); @Transient private Set<String> dirtyProperties = new LinkedHashSet<String>(); public SelfDirtyCheckingEntity() { try { BeanInfo beanInfo = Introspector.getBeanInfo(getClass()); PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor descriptor : descriptors) { Method setter = descriptor.getWriteMethod(); if (setter != null ) { setterToPropertyMap.put(setter.getName(), descriptor.getName()); } } } catch (IntrospectionException e) { throw new IllegalStateException(e); } } @Override public Set<String> getDirtyProperties() { return dirtyProperties; } @Override public void clearDirtyProperties() { dirtyProperties.clear(); } protected void markDirtyProperty() { String methodName = Thread.currentThread().getStackTrace()[ 2 ].getMethodName(); dirtyProperties.add(setterToPropertyMap.get(methodName)); } } |
Все объекты, проверяющие грязную проверку вручную, должны расширять этот базовый класс и явно помечать грязные свойства посредством вызова метода markDirtyProperty .
Фактически самозагрязненный объект проверки выглядит так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
@Entity @Table (name = "ORDER_LINE" ) public class OrderLine extends SelfDirtyCheckingEntity { @Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; private Long number; private String orderedBy; private Date orderedOn; public Long getId() { return id; } public Long getNumber() { return number; } public void setNumber(Long number) { this .number = number; markDirtyProperty(); } public String getOrderedBy() { return orderedBy; } public void setOrderedBy(String orderedBy) { this .orderedBy = orderedBy; markDirtyProperty(); } public Date getOrderedOn() { return orderedOn; } public void setOrderedOn(Date orderedOn) { this .orderedOn = orderedOn; markDirtyProperty(); } } |
Всякий раз, когда вызывается сеттер, связанное свойство становится грязным. Для простоты это простое упражнение не охватывает случай использования, когда мы возвращаем свойство к его первоначальному значению.
Грязный проверочный тест
Чтобы проверить механизмы самозагрязненной проверки, я собираюсь запустить следующий тестовый пример:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
@Test public void testDirtyChecking() { doInTransaction( new TransactionCallable<Void>() { @Override public Void execute(Session session) { OrderLine orderLine = new OrderLine(); session.persist(orderLine); session.flush(); orderLine.setNumber(123L); orderLine.setOrderedBy( "Vlad" ); orderLine.setOrderedOn( new Date()); session.flush(); orderLine.setOrderedBy( "Alex" ); return null ; } }); } |
Решение Hibernate Interceptor
Обратный вызов Hibernate Interceptor findDirty позволяет нам контролировать процесс обнаружения грязных свойств. Этот метод может вернуть:
- null , чтобы делегировать грязную проверку стратегии по умолчанию Hibernate
- массив int [], содержащий измененные свойства
Наш перехватчик Hibernate выглядит так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
public class DirtyCheckingInterceptor extends EmptyInterceptor { @Override public int [] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) { if (entity instanceof DirtyAware) { DirtyAware dirtyAware = (DirtyAware) entity; Set<String> dirtyProperties = dirtyAware.getDirtyProperties(); int [] dirtyPropertiesIndices = new int [dirtyProperties.size()]; List<String> propertyNamesList = Arrays.asList(propertyNames); int i = 0 ; for (String dirtyProperty : dirtyProperties) { LOGGER.info( "The {} property is dirty" , dirtyProperty); dirtyPropertiesIndices[i++] = propertyNamesList.indexOf(dirtyProperty); } dirtyAware.clearDirtyProperties(); return dirtyPropertiesIndices; } return super .findDirty(entity, id, currentState, previousState, propertyNames, types); } } |
При передаче этого перехватчика в нашу текущую конфигурацию SessionFactory мы получаем следующий вывод:
1
2
3
4
5
6
7
8
|
INFO [main]: c. v .h.m.l.f.InterceptorDirtyCheckingTest - The number property is dirty INFO [main]: c. v .h.m.l.f.InterceptorDirtyCheckingTest - The orderedBy property is dirty INFO [main]: c. v .h.m.l.f.InterceptorDirtyCheckingTest - The orderedOn property is dirty DEBUG [main]: o.h.e.i.AbstractFlushingEventListener - Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects DEBUG [main]: n.t.d.l.SLF4JQueryLoggingListener - Name: Time:1 Num:1 Query:{[update ORDER_LINE set number=?, orderedBy=?, orderedOn=? where id =?][123,Vlad,2014-08-20 07:35:05.649,1]} INFO [main]: c. v .h.m.l.f.InterceptorDirtyCheckingTest - The orderedBy property is dirty DEBUG [main]: o.h.e.i.AbstractFlushingEventListener - Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects DEBUG [main]: n.t.d.l.SLF4JQueryLoggingListener - Name: Time:0 Num:1 Query:{[update ORDER_LINE set number=?, orderedBy=?, orderedOn=? where id =?][123,Alex,2014-08-20 07:35:05.649,1]} |
Механизм ручной проверки грязи обнаружил входящие изменения и распространил их на слушатель события очистки.
Менее известный CustomEntityDirtinessStrategy
CustomEntityDirtinessStrategy — это недавнее дополнение API Hibernate, позволяющее нам обеспечить механизм грязной проверки для конкретного приложения. Этот интерфейс может быть реализован следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public static class EntityDirtinessStrategy implements CustomEntityDirtinessStrategy { @Override public boolean canDirtyCheck(Object entity, EntityPersister persister, Session session) { return entity instanceof DirtyAware; } @Override public boolean isDirty(Object entity, EntityPersister persister, Session session) { return !cast(entity).getDirtyProperties().isEmpty(); } @Override public void resetDirty(Object entity, EntityPersister persister, Session session) { cast(entity).clearDirtyProperties(); } @Override public void findDirty(Object entity, EntityPersister persister, Session session, DirtyCheckContext dirtyCheckContext) { final DirtyAware dirtyAware = cast(entity); dirtyCheckContext.doDirtyChecking( new AttributeChecker() { @Override public boolean isDirty(AttributeInformation attributeInformation) { String propertyName = attributeInformation.getName(); boolean dirty = dirtyAware.getDirtyProperties().contains( propertyName ); if (dirty) { LOGGER.info( "The {} property is dirty" , propertyName); } return dirty; } } ); } private DirtyAware cast(Object entity) { return DirtyAware. class .cast(entity); } } |
Чтобы зарегистрировать реализацию CustomEntityDirtinessStrategy, мы должны установить следующее свойство Hibernate:
1
|
properties.setProperty( "hibernate.entity_dirtiness_strategy" , EntityDirtinessStrategy. class .getName()); |
Выполнение нашего теста дает следующий вывод:
1
2
3
4
5
6
7
8
|
INFO [main]: c. v .h.m.l.f.CustomEntityDirtinessStrategyTest - The number property is dirty INFO [main]: c. v .h.m.l.f.CustomEntityDirtinessStrategyTest - The orderedBy property is dirty INFO [main]: c. v .h.m.l.f.CustomEntityDirtinessStrategyTest - The orderedOn property is dirty DEBUG [main]: o.h.e.i.AbstractFlushingEventListener - Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects DEBUG [main]: n.t.d.l.SLF4JQueryLoggingListener - Name: Time:1 Num:1 Query:{[update ORDER_LINE set number=?, orderedBy=?, orderedOn=? where id =?][123,Vlad,2014-08-20 12:51:30.068,1]} INFO [main]: c. v .h.m.l.f.CustomEntityDirtinessStrategyTest - The orderedBy property is dirty DEBUG [main]: o.h.e.i.AbstractFlushingEventListener - Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects DEBUG [main]: n.t.d.l.SLF4JQueryLoggingListener - Name: Time:0 Num:1 Query:{[update ORDER_LINE set number=?, orderedBy=?, orderedOn=? where id =?][123,Alex,2014-08-20 12:51:30.068,1]} |
Вывод
Хотя проверки на уровне поля по умолчанию или альтернативы инструментарию байт-кода достаточно для большинства приложений, могут возникнуть ситуации, когда вы захотите получить контроль над процессом обнаружения изменений. В долгосрочном проекте нередко настраивают определенные встроенные механизмы для удовлетворения исключительных требований к качеству обслуживания. Решение о принятии фреймворка также должно учитывать возможность расширения фреймворка и настройки.
- Код доступен на GitHub .
Ссылка: | Как настроить механизм грязной проверки Hibernate от нашего партнера JCG Влада Михалча в блоге Влада Михалча . |