Jvm runtime interface Pojo and Repository generator
is a protocol defined to use Interface as Pojo.
Note: this implement by JDK dynamic proxy, and will decrement performance for about 10 times, may never use for performance award condition. It's best use for slow business logic implement that coding time is more expensive than performance requirement.
there includes new implement with ByteBuddy, performance improved,see benchmark to choose.
Property: defined by interface Getter&Setter methods.
Getter&Setter Strategy:
Common Strategy:
1. getter must be PUBLIC, NONE DEFAULT, NO PARAMETER and Returns NONE VOID type.
2. setter must be PUBLIC, NONE DEFAULT, HAVE ONLY ONE PARAMETER and Returns VOID type Or SELF.
Fluent Strategy (The default strategy): getter's name and setter's name are the property name;
Java Bean Strategy: getter's name is prefixed with 'get' or 'is', setter's name is prefixed with 'set'; this would be enabled by annotate with {@link JavaBean} and set {@link JavaBean#value()} as false
Mix Strategy: both Java Bean Strategy and Fluent Strategy are allowed; this would be enabled by annotate with {@link JavaBean} and set {@link JavaBean#value()} as true
default methods
underlyingMap: fetch this underlying storage, which is mutable, but should careful to change its values, cause of this will ignore any annotated validation or convection methods on interface.
underlyingChangedProperties: recoding the set action took on properties. only effect by using {@link Dao}, under other conditions it always returns null.
Misc
Inherit: Mimic can inherit from other Mimic
Conversion: Mimic can annotate with {@link AsString} on getter or setter to enable single property conversion.
for Collections(LIST,SET and ARRAY), there is {@link Array} to support nested Mimicked properties. but current {@link Map} is not been supported.
Validation: Mimic can annotate with {@link Validation} on getter or setter to enable single property validation.
Mimic also use overrideable method {@link Mimic#validate()} to active a Pojo validation.
Extension: {@link Dao} is extension for use {@link Mimic} as easy Jooq repository.
is a Jooq repository interface for {@link Mimic}.
Dao extends with Jooq Dynamic api to create Repository interface for a {@link Mimic}.
Note:
Dao can not be inherited. that means a Dao must directly extended {@link Dao}.
{@link Mimic} used by Dao must directly annotate with {@link Dao.Entity}.
{@link Mimic} will enable Property Change Recording, which store changed Property Name in {@link Mimic#underlyingChangedProperties()}.
- simple benchmark:
I7-4790K@4.00GHz 16G WIN7 x64
Benchmark Mode Cnt Score Error Units
----------------------------------------------------------------------------------
MimicBenchmark.mimicBenchPojoBuildOneShot ss 50 1399.364 ± 250.398 ns/op
MimicBenchmark.mimicBenchAsmBuildOneShot ss 50 24174.228 ± 4984.977 ns/op
MimicBenchmark.mimicBenchProxyBuildOneShot ss 50 20351.224 ± 3494.686 ns/op
MimicBenchmark.mimicBenchPojoBuild avgt 50 71.325 ± 11.814 ns/op
MimicBenchmark.mimicBenchAsmBuild** avgt 50 792.614 ± 109.759 ns/op
MimicBenchmark.mimicBenchProxyBuild** avgt 50 385.281 ± 71.375 ns/op
MimicBenchmark.mimicBenchPojoGet avgt 50 3.179 ± 0.168 ns/op
MimicBenchmark.mimicBenchAsmGet avgt 50 3.190 ± 0.156 ns/op
MimicBenchmark.mimicBenchProxyGet avgt 50 21.378 ± 1.250 ns/op
MimicBenchmark.mimicBenchPojoGetConv* avgt 50 14.621 ± 0.500 ns/op
MimicBenchmark.mimicBenchAsmGetConv avgt 50 3.818 ± 0.185 ns/op
MimicBenchmark.mimicBenchProxyGetConv avgt 50 35.102 ± 1.934 ns/op
MimicBenchmark.mimicBenchPojoSet avgt 50 9.926 ± 0.495 ns/op
MimicBenchmark.mimicBenchAsmSet avgt 50 15.776 ± 0.665 ns/op
MimicBenchmark.mimicBenchProxySet avgt 50 55.353 ± 4.208 ns/op
MimicBenchmark.mimicBenchPojoSetConv avgt 50 55.036 ± 6.174 ns/op
MimicBenchmark.mimicBenchAsmSetConv* avgt 50 18.637 ± 0.825 ns/op
MimicBenchmark.mimicBenchProxySetConv avgt 50 90.640 ± 6.061 ns/op
Intel(R) Xeon(R) CPU E5-2678 v3 @ 2.50GHz X2 Linux X64
MimicBenchmark.mimicBenchPojoBuildOneShot ss 50 7557.820 ± 7871.119 ns/op
MimicBenchmark.mimicBenchAsmBuildOneShot ss 50 91394.300 ± 50905.101 ns/op
MimicBenchmark.mimicBenchProxyBuildOneShot ss 50 62893.256 ± 30947.078 ns/op
MimicBenchmark.mimicBenchPojoBuild avgt 50 55.184 ± 34.656 ns/op
MimicBenchmark.mimicBenchAsmBuild ** avgt 50 2729.979 ± 489.367 ns/op
MimicBenchmark.mimicBenchProxyBuild ** avgt 50 4643.816 ± 9103.532 ns/op
MimicBenchmark.mimicBenchPojoGet avgt 50 3.630 ± 0.159 ns/op
MimicBenchmark.mimicBenchAsmGet avgt 50 3.820 ± 0.193 ns/op
MimicBenchmark.mimicBenchProxyGet avgt 50 25.273 ± 1.166 ns/op
MimicBenchmark.mimicBenchPojoGetConv * avgt 50 325.344 ± 215.961 ns/op
MimicBenchmark.mimicBenchAsmGetConv * avgt 50 4.523 ± 0.221 ns/op
MimicBenchmark.mimicBenchProxyGetConv * avgt 50 358.351 ± 239.475 ns/op
MimicBenchmark.mimicBenchPojoSet avgt 50 12.567 ± 0.432 ns/op
MimicBenchmark.mimicBenchAsmSet avgt 50 22.138 ± 1.440 ns/op
MimicBenchmark.mimicBenchProxySet avgt 50 2467.734 ± 271.575 ns/op
MimicBenchmark.mimicBenchPojoSetConv avgt 50 92.628 ± 43.897 ns/op
MimicBenchmark.mimicBenchAsmSetConv avgt 50 25.559 ± 1.064 ns/op
MimicBenchmark.mimicBenchProxySetConv avgt 50 168.255 ± 90.126 ns/op
----------------------------------------------------------------------------------
* ASM setter use a lazy convert when generate map, but pojo do validate and convert when set or get.
** Average build time is the cost without interface analysis and internal Objects creation,those are cached with Caffeine
org.slf4j:slf4j-api:2.x
use for loggingorg.jooq:jool:0.9.x
use for tuples and Seq, maybe removed future.com.github.ben-manes.caffeine:caffeine:3.x
use for cache.
net.bytebuddy:byte-buddy:1.x
: needed to generate ASM by useByteASM
mode.org.jooq:jooq:3.x
: needed when useDAO
extension.
<dependencies>
<!-- only requirement for just use JDK proxy mode -->
<dependency>
<groupId>io.github.zenliucn.java</groupId>
<artifactId>mimic</artifactId>
<version>${mimic.version}</version>
</dependency>
<!-- needed to use ByteASM mode -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.17</version>
</dependency>
<!-- needed to use DAO extension -->
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq</artifactId>
<version>3.14.7</version>
</dependency>
<!-- use as database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>
<!-- use as connection pool -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.7.9</version>
</dependency>
</dependencies>
define interface as data structure
//define a interface
import cn.zenliu.java.mimic.Mimic;
import java.util.Map;
@Mimic.Dao.Entity //only want to use a Jooq DAO
public interface Fluent extends Mimic {
long id();
void id(long val);
@Validation(property = "notNull")
Long identity();
Fluent identity(Long val);
@Dao.AsString
// this field will store as String
Long idOfUser();
Fluent idOfUser(Long val);
@Override
default void validate() throws IllegalStateException {
if (identity() > 10) {
throw new IllegalStateException("identity must less than 10");
}
}
static Fluent of(Map<String, Object> value) {
return Mimic.newInstance(Fluent.class, value);
}
}
define the JOOQ DAO
interface FluentDao extends Mimic.Dao<Fluent> {
//the static factory method
static FluentDao of(Configuration cfg) {
return Dao.newInstance(Fluent.class, FluentDao.class, cfg);
}
//define the Identity column
@As(typeHolder = Dao.class, typeProperty = "BigIntIdentity")
Field<Long> id();
//define store as BIGINT, not really need this, common type will guess by JOOQ
@As(typeHolder = SQLDataType.class, typeProperty = "BIGINT")
Field<Long> identity();
@As(typeHolder = SQLDataType.class, typeProperty = "VARCHAR")
Field<String> idOfUser();
//optional override to supply all fields, default maybe not with supposed order
@Override
default List<Field<?>> allFields() {
return Arrays.asList(id(), identity(), idOfUser());
}
//extend DAO actions
default int insert(Fluent i) {
return ctx().insertInto(table()).set(toDatabase(i.underlyingMap())).execute();
}
default Fluent fetchById(long id) {
return instance(ctx().selectFrom(table()).where(id().eq(id)).fetchOne().intoMap());
}
default Fluent fetchByIdentity(long identity) {
return instance(ctx().selectFrom(table()).where(identity().eq(identity)).fetchOne().intoMap());
}
default Fluent update(Fluent i) {
if (i.id() == 0) {
throw new IllegalStateException("can't update entity have no id");
}
val und = i.underlyingMap();
val changes = i.underlyingChangedProperties();
if (changes == null || changes.isEmpty()) throw new IllegalStateException("nothing to update entity");
val m = new HashMap<String, Object>();
for (String property : changes) {
m.put(property, und.get(property));
}
if (ctx().update(table()).set(toDatabase(m)).where(id().eq(i.id())).execute() < 1) {
throw new IllegalStateException("update failure");
}
return i;
}
default void deleteAll() {
ctx().delete(table()).execute();
}
}
use them
@Slf4j
public class Launcher {
static final DefaultConfiguration cfg;
static {
Config.cacheSize.set(500);
cfg = new DefaultConfiguration();
cfg.setSQLDialect(SQLDialect.H2);
val hc = new HikariConfig();
hc.setJdbcUrl("jdbc:h2:mem:test");
cfg.setDataSource(new HikariDataSource(hc));
}
public static void main(String[] args) {
val v = Fluent.of(null);
log.info("create {}", v);
v.id(12L);
v.identity(12L);
log.info("add id: {}", v);
log.info("class is : {}", v.getClass());
val dao = Fluent.FluentDao.of(cfg);
log.info("dao {}", dao);
log.info("dao class {}", dao.getClass());
log.info("ddl result: {}", dao.DDL());
log.info("insert result: {}", dao.insert(v));
log.info("select result: {}", dao.fetchById(12L));
log.info("select result: {}", dao.fetchByIdentity(12L));
}
}
More sample just check out maven project in sample
directory. There may be a small example Project to
use mimic+h2+jooq+reactor-netty
as micro webservice, if got time.
current master branch will keep with the nearest release code and two branches added for:
dev/1.1.x
: old code that support java 8dev/1.2.x
: new code that support at least Java 17