Hi Lucas, I have been watching your project for a while now and have to say that you are dying a fantastic job! (...have you got a full time job as well?)
Couple of months back I decided to use jOOQ (for all the obvious reasons)
in my project. I am now at the
point when I have got a decent piece of functionality implemented and
started to write some high level tests
for my use case. Each tests runs with a simple DYI (do it yourself)
profiler that gives me time breakdown
for the relevant methods within the execution stack.
During these tests I have first noticed that it takes disproportionally
long time to construct the Factory class.
To give you an example I have a test case that inserts 12 records for 7
different entity types (in other words
12 records into 7 tables). The whole test runs 266 ms and out of this 140
ms is spent to construct the Factory
(with a JDBC connection and H2 Dialect in my case). After checking the
source code I did not find anything there
that should take so long so I added a couple of traces there and run it
through the debugger. I have noticed
that after the initial Factory constructor call there are additional 15
calls as well. These calls come from
the default static initialiser that is run for each database dialect.
static {
for (SQLDialect dialect : SQLDialect.values()) {
Factory.DEFAULT_INSTANCES[dialect.ordinal()] = new
Factory(dialect);
}
}
All in all one could say that this is not a big deal as you construct the
factory once (and incur this cost
only once) but since the Factory is constructed with the requested dialect
it seems a bit pointless to have this
done for all available dialects.
The side effect/product of the added traces in the Factory constructor
brought up another issue. The Factory
constructor is called multiple times after the initial instance of the
Factory is created. In this specific test case
there are additional 110 calls. The story starts with the construction of
the insert statement - in my case one
of them looks like this:
return ftry
.insertInto(CATEGORY,
CATEGORY.ID,
CATEGORY.USR,
CATEGORY.VER,
CATEGORY.TYPE,
CATEGORY.NAME)
.values(ent.getId(),
ent.getUser(),
newVer,
ent.getEnum(Property.CategoryType),
ent.getString(Property.Name));
Which eventually leads to adding key/value pairs to a linked hash map that
calls hashCode() on TableFiedlImp class
that is implemented in the AbstractQueryPart class:
@Override
public int hashCode() {
// This is a working default implementation. It should be
overridden by
// concrete subclasses, to improve performance
return create().renderInlined(this).hashCode();
}
Seems like something got forgotten here as this default implementation
calls:
protected final Factory create() {
return create(getConfiguration());
}
On the Factory class which then calls:
protected final Factory create(Configuration configuration) {
return Factory.getNewFactory(configuration);
}
And then the constructor:
@SuppressWarnings("deprecation")
final static Factory getNewFactory(Configuration configuration) {
if (configuration == null) {
return
getNewFactory(DefaultConfiguration.DEFAULT_CONFIGURATION);
}
else {
return new Factory(
configuration.getDataSource(),
configuration.getConnection(),
configuration.getDialect(),
configuration.getSettings(),
configuration.getSchemaMapping(),
configuration.getData());
}
}
The bottom line is that the constructor is called for each column of each
record that is constructed.
(see the attached snapshot for the whole stack trace). I have not profiled
any updates as yet but it
seems likely that the behaviour will be the same as they most likely reuse
the same functionality.
Regards,
Adam
PS
It is a great shame that there is not a simple JUnit profiler add-on
available which would show these
kinds of issue straight away - any takers?
<<attachment: stack-call.png>>
