Статьи

Hibernate улучшение байт-кода

Вступление

Теперь, когда вы знакомы с основами проверки загрязненности в Hibernate , мы можем углубиться в усовершенствованные механизмы проверки. Хотя алгоритм обхода графа по умолчанию может быть достаточным для большинства сценариев использования, могут быть ситуации, когда вам нужен оптимизированный алгоритм грязной проверки, и инструментарий намного удобнее, чем создание собственной пользовательской стратегии .

Использование Ant Hibernate Tools

Традиционно инструменты Hibernate были ориентированы на Ant и Eclipse. Инструментарий для байт-кода стал возможен начиная с Hibernate 3 , но для запуска подпрограмм улучшения байт-кода CGLIB или Javassist требовалась задача Ant.

Maven поддерживает запуск задач Ant через плагин maven-antrun :

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
<build>
    <plugins>
        <plugin>
            <artifactId>maven-antrun-plugin</artifactId>
            <executions>
                <execution>
                    <id>Instrument domain classes</id>
                    <configuration>
                        <tasks>
                            <taskdef name="instrument"
                                     classname="org.hibernate.tool.instrument.javassist.InstrumentTask">
                                <classpath>
                                    <path refid="maven.dependency.classpath"/>
                                    <path refid="maven.plugin.classpath"/>
                                </classpath>
                            </taskdef>
                            <instrument verbose="true">
                                <fileset dir="${project.build.outputDirectory}">
                                    <include name="**/flushing/*.class"/>
                                </fileset>
                            </instrument>
                        </tasks>
                    </configuration>
                    <phase>process-classes</phase>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
            <dependencies>
                <dependency>
                    <groupId>org.hibernate</groupId>
                    <artifactId>hibernate-core</artifactId>
                    <version>${hibernate.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.javassist</groupId>
                    <artifactId>javassist</artifactId>
                    <version>${javassist.version}</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

Так для следующего класса источника сущности:

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
@Entity
public class EnhancedOrderLine {
 
    @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;
    }
 
    public String getOrderedBy() {
        return orderedBy;
    }
 
    public void setOrderedBy(String orderedBy) {
        this.orderedBy = orderedBy;
    }
 
    public Date getOrderedOn() {
        return orderedOn;
    }
 
    public void setOrderedOn(Date orderedOn) {
        this.orderedOn = orderedOn;
    }
}

Во время сборки генерируется следующий класс:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
@Entity
public class EnhancedOrderLine implements FieldHandled {
 
  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  private Long id;
  private Long number;
  private String orderedBy;
  private Date orderedOn;
  private transient FieldHandler $JAVASSIST_READ_WRITE_HANDLER;
 
  public Long getId() {
    return $javassist_read_id();
  }
 
  public Long getNumber() {
    return $javassist_read_number();
  }
 
  public void setNumber(Long number) {
    $javassist_write_number(number);
  }
 
  public String getOrderedBy() {
    return $javassist_read_orderedBy();
  }
 
  public void setOrderedBy(String orderedBy) {
    $javassist_write_orderedBy(orderedBy);
  }
 
  public Date getOrderedOn() {
    return $javassist_read_orderedOn();
  }
 
  public void setOrderedOn(Date orderedOn) {
    $javassist_write_orderedOn(orderedOn);
  }
 
  public FieldHandler getFieldHandler() {
    return this.$JAVASSIST_READ_WRITE_HANDLER;
  }
 
  public void setFieldHandler(FieldHandler paramFieldHandler) {
    this.$JAVASSIST_READ_WRITE_HANDLER = paramFieldHandler;
  }
 
  public Long $javassist_read_id() {
    if (getFieldHandler() == null)
      return this.id;
  }
 
  public void $javassist_write_id(Long paramLong) {
    if (getFieldHandler() == null) {
      this.id = paramLong;
      return;
    }
    this.id = ((Long)getFieldHandler().writeObject(this, "id", this.id, paramLong));
  }
 
  public Long $javassist_read_number() {
    if (getFieldHandler() == null)
      return this.number;
  }
 
  public void $javassist_write_number(Long paramLong) {
    if (getFieldHandler() == null) {
      this.number = paramLong;
      return;
    }
    this.number = ((Long)getFieldHandler().writeObject(this, "number", this.number, paramLong));
  }
 
  public String $javassist_read_orderedBy() {
    if (getFieldHandler() == null)
      return this.orderedBy;
  }
 
  public void $javassist_write_orderedBy(String paramString) {
    if (getFieldHandler() == null) {
      this.orderedBy = paramString;
      return;
    }
    this.orderedBy = ((String)getFieldHandler().writeObject(this, "orderedBy", this.orderedBy, paramString));
  }
 
  public Date $javassist_read_orderedOn() {
    if (getFieldHandler() == null)
      return this.orderedOn;
  }
 
  public void $javassist_write_orderedOn(Date paramDate) {
    if (getFieldHandler() == null) {
      this.orderedOn = paramDate;
      return;
    }
    this.orderedOn = ((Date)getFieldHandler().writeObject(this, "orderedOn", this.orderedOn, paramDate));
  }
}

Хотя org.hibernate.bytecode.instrumentation.spi.AbstractFieldInterceptor удается перехватить грязные поля, эта информация никогда не запрашивается во время отслеживания грязных объектов .

Усовершенствование байт-кода InstrumentTask может только сказать, является ли объект грязным, не хватает поддержки для указания того, какие свойства были изменены, что делает InstrumentTask более подходящим для стратегии извлечения LAZY «без прокси» .

Зимуют-повышения-Maven-плагин

В Hibernate 4.2.8 добавлена ​​поддержка выделенного плагина улучшения байт-кода Maven .

Плагин улучшения байт-кода Maven прост в настройке:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<build>
    <plugins>
        <plugin>
            <groupId>org.hibernate.orm.tooling</groupId>
            <artifactId>hibernate-enhance-maven-plugin</artifactId>
            <executions>
                 <execution>
                     <phase>compile</phase>
                     <goals>
                         <goal>enhance</goal>
                     </goals>
                 </execution>
             </executions>
        </plugin>
    </plugins>
</build>

Во время сборки проекта генерируется следующий класс:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
@Entity
public class EnhancedOrderLine
        implements ManagedEntity, PersistentAttributeInterceptable, SelfDirtinessTracker {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private Long number;
    private String orderedBy;
    private Date orderedOn;
 
    @Transient
    private transient PersistentAttributeInterceptor $$_hibernate_attributeInterceptor;
 
    @Transient
    private transient Set $$_hibernate_tracker;
 
    @Transient
    private transient CollectionTracker $$_hibernate_collectionTracker;
 
    @Transient
    private transient EntityEntry $$_hibernate_entityEntryHolder;
 
    @Transient
    private transient ManagedEntity $$_hibernate_previousManagedEntity;
 
    @Transient
    private transient ManagedEntity $$_hibernate_nextManagedEntity;
 
    public Long getId() {
        return $$_hibernate_read_id();
    }
 
    public Long getNumber() {
        return $$_hibernate_read_number();
    }
 
    public void setNumber(Long number) {
        $$_hibernate_write_number(number);
    }
 
    public String getOrderedBy() {
        return $$_hibernate_read_orderedBy();
    }
 
    public void setOrderedBy(String orderedBy) {
        $$_hibernate_write_orderedBy(orderedBy);
    }
 
    public Date getOrderedOn() {
        return $$_hibernate_read_orderedOn();
    }
 
    public void setOrderedOn(Date orderedOn) {
        $$_hibernate_write_orderedOn(orderedOn);
    }
 
    public PersistentAttributeInterceptor $$_hibernate_getInterceptor() {
        return this.$$_hibernate_attributeInterceptor;
    }
 
    public void $$_hibernate_setInterceptor(PersistentAttributeInterceptor paramPersistentAttributeInterceptor) {
        this.$$_hibernate_attributeInterceptor = paramPersistentAttributeInterceptor;
    }
 
    public void $$_hibernate_trackChange(String paramString) {
        if (this.$$_hibernate_tracker == null)
            this.$$_hibernate_tracker = new HashSet();
        if (!this.$$_hibernate_tracker.contains(paramString))
            this.$$_hibernate_tracker.add(paramString);
    }
 
    private boolean $$_hibernate_areCollectionFieldsDirty() {
        return ($$_hibernate_getInterceptor() != null) && (this.$$_hibernate_collectionTracker != null);
    }
 
    private void $$_hibernate_getCollectionFieldDirtyNames(Set paramSet) {
        if (this.$$_hibernate_collectionTracker == null)
            return;
    }
 
    public boolean $$_hibernate_hasDirtyAttributes() {
        return ((this.$$_hibernate_tracker == null) || (this.$$_hibernate_tracker.isEmpty())) && ($$_hibernate_areCollectionFieldsDirty());
    }
 
    private void $$_hibernate_clearDirtyCollectionNames() {
        if (this.$$_hibernate_collectionTracker == null)
            this.$$_hibernate_collectionTracker = new CollectionTracker();
    }
 
    public void $$_hibernate_clearDirtyAttributes() {
        if (this.$$_hibernate_tracker != null)
            this.$$_hibernate_tracker.clear();
        $$_hibernate_clearDirtyCollectionNames();
    }
 
    public Set<String> $$_hibernate_getDirtyAttributes() {
        if (this.$$_hibernate_tracker == null)
            this.$$_hibernate_tracker = new HashSet();
        $$_hibernate_getCollectionFieldDirtyNames(this.$$_hibernate_tracker);
        return this.$$_hibernate_tracker;
    }
 
    private Long $$_hibernate_read_id() {
        if ($$_hibernate_getInterceptor() != null)
            this.id = ((Long) $$_hibernate_getInterceptor().readObject(this, "id", this.id));
        return this.id;
    }
 
    private void $$_hibernate_write_id(Long paramLong) {
        if (($$_hibernate_getInterceptor() == null) || ((this.id == null) || (this.id.equals(paramLong))))
            break label39;
        $$_hibernate_trackChange("id");
        label39:
        Long localLong = paramLong;
        if ($$_hibernate_getInterceptor() != null)
            localLong = (Long) $$_hibernate_getInterceptor().writeObject(this, "id", this.id, paramLong);
        this.id = localLong;
    }
 
    private Long $$_hibernate_read_number() {
        if ($$_hibernate_getInterceptor() != null)
            this.number = ((Long) $$_hibernate_getInterceptor().readObject(this, "number", this.number));
        return this.number;
    }
 
    private void $$_hibernate_write_number(Long paramLong) {
        if (($$_hibernate_getInterceptor() == null) || ((this.number == null) || (this.number.equals(paramLong))))
            break label39;
        $$_hibernate_trackChange("number");
        label39:
        Long localLong = paramLong;
        if ($$_hibernate_getInterceptor() != null)
            localLong = (Long) $$_hibernate_getInterceptor().writeObject(this, "number", this.number, paramLong);
        this.number = localLong;
    }
 
    private String $$_hibernate_read_orderedBy() {
        if ($$_hibernate_getInterceptor() != null)
            this.orderedBy = ((String) $$_hibernate_getInterceptor().readObject(this, "orderedBy", this.orderedBy));
        return this.orderedBy;
    }
 
    private void $$_hibernate_write_orderedBy(String paramString) {
        if (($$_hibernate_getInterceptor() == null) || ((this.orderedBy == null) || (this.orderedBy.equals(paramString))))
            break label39;
        $$_hibernate_trackChange("orderedBy");
        label39:
        String str = paramString;
        if ($$_hibernate_getInterceptor() != null)
            str = (String) $$_hibernate_getInterceptor().writeObject(this, "orderedBy", this.orderedBy, paramString);
        this.orderedBy = str;
    }
 
    private Date $$_hibernate_read_orderedOn() {
        if ($$_hibernate_getInterceptor() != null)
            this.orderedOn = ((Date) $$_hibernate_getInterceptor().readObject(this, "orderedOn", this.orderedOn));
        return this.orderedOn;
    }
 
    private void $$_hibernate_write_orderedOn(Date paramDate) {
        if (($$_hibernate_getInterceptor() == null) || ((this.orderedOn == null) || (this.orderedOn.equals(paramDate))))
            break label39;
        $$_hibernate_trackChange("orderedOn");
        label39:
        Date localDate = paramDate;
        if ($$_hibernate_getInterceptor() != null)
            localDate = (Date) $$_hibernate_getInterceptor().writeObject(this, "orderedOn", this.orderedOn, paramDate);
        this.orderedOn = localDate;
    }
 
    public Object $$_hibernate_getEntityInstance() {
        return this;
    }
 
    public EntityEntry $$_hibernate_getEntityEntry() {
        return this.$$_hibernate_entityEntryHolder;
    }
 
    public void $$_hibernate_setEntityEntry(EntityEntry paramEntityEntry) {
        this.$$_hibernate_entityEntryHolder = paramEntityEntry;
    }
 
    public ManagedEntity $$_hibernate_getPreviousManagedEntity() {
        return this.$$_hibernate_previousManagedEntity;
    }
 
    public void $$_hibernate_setPreviousManagedEntity(ManagedEntity paramManagedEntity) {
        this.$$_hibernate_previousManagedEntity = paramManagedEntity;
    }
 
    public ManagedEntity $$_hibernate_getNextManagedEntity() {
        return this.$$_hibernate_nextManagedEntity;
    }
 
    public void $$_hibernate_setNextManagedEntity(ManagedEntity paramManagedEntity) {
        this.$$_hibernate_nextManagedEntity = paramManagedEntity;
    }
}

Легко понять, что новая логика улучшения байт-кода отличается от логики, созданной предыдущей InstrumentTask.

Как и пользовательский механизм грязной проверки , новая версия улучшения байт-кода записывает, какие свойства изменились, а не просто простой грязный флаг. Логика расширения помечает грязные поля при изменении. Этот подход гораздо более эффективен, чем сравнение всех текущих значений свойств с данными моментального снимка во время загрузки.

Мы уже на месте?

Даже если байт-код класса сущности улучшается, каким-то образом в Hibernate 4.3.6 все еще не хватает частей головоломки .

Например, при вызове setNumber (Long number) выполняется следующий метод перехвата:

01
02
03
04
05
06
07
08
09
10
private void $$_hibernate_write_number(Long paramLong) {
        if (($$_hibernate_getInterceptor() == null) || ((this.number == null) || (this.number.equals(paramLong))))
            break label39;
        $$_hibernate_trackChange("number");
        label39:
        Long localLong = paramLong;
        if ($$_hibernate_getInterceptor() != null)
            localLong = (Long) $$_hibernate_getInterceptor().writeObject(this, "number", this.number, paramLong);
        this.number = localLong;
    }

В моих примерах $$ _ hibernate_getInterceptor () всегда имеет значение null, что обходит вызов $$ _ hibernate_trackChange («number») . Из-за этого грязное свойство записываться не будет, что заставит Hibernate использовать стандартный алгоритм грязной проверки по умолчанию для глубокого сравнения .

Таким образом, даже если Hibernate добился значительного прогресса в этой конкретной области, усовершенствование грязной проверки все еще требует дополнительной работы, чтобы стать доступным.

  • Код доступен на GitHub .