Skip to content

QinArmy/army

Repository files navigation

Army is a better blocking/reactive SQL framework

Sonatype Nexus (Snapshots) Maven Central Apache 2.0 Java support

Design Philosophy

  1. Don't create new world,just mapping real world.
  2. We need standard,we need dialect,it's real world.
  3. We need blocking way,we need reactive way,it's real world.

Army convention

  1. The interface( or class) that start with underline is army framework private interface( or class)

How to start ?

Maven

<dependency>
    <groupId>io.qinarmy</groupId>
    <artifactId>army-jdbc</artifactId>
    <version>0.6.6</version>
</dependency>
appropriate maven module that contain domain class
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <annotationProcessors>io.army.modelgen.ArmyMetaModelDomainProcessor</annotationProcessors>
            </configuration>
        </plugin>
    </plugins>
</build>

Using army annotations mapping pojo ChinaRegion to china_region table

// mapping domain to table

@Table(name = "china_region", indexes = {
        @Index(name = "china_region_uni_name_region_type", fieldList = {"name", "regionType"}, unique = true),
        @Index(name = "china_region_inx_parent_id", fieldList = "parentId")},
        comment = "china region")
@Inheritance("regionType")
public class ChinaRegion<T extends ChinaRegion<T>> {  // assume ChinaRegion exists sub class 


    @Generator(type = GeneratorType.POST) // id is generated by database
    @Column
    private Long id; // 'id' is army reserved field name,it representing primary key

    @Column(scale = 2)
    private LocalDateTime createTime;  // 'createTime' is army reserved field name,it representing table record create time

    // updateTime is optional field, if table is immutable
    @Column
    private LocalDateTime updateTime; // 'updateTime' is army reserved field name,it representing table record last update time

    // version is optional field
    @Column
    private Integer version; // 'version' is army reserved field name,it representing table record's optimistic lock

    // visible is optional field
    @Column
    private Boolean visible; // 'visible' is army reserved field name,it representing table record's soft delete

    @Column
    private RegionType regionType;

    @Column(precision = 20, nullable = false, comment = "china region name")
    private String name;

    @Column(precision = 16, scale = 2, defaultValue = "0.00", nullable = false, comment = "china region GDP")
    private BigDecimal regionGdp;


    @Column(nullable = false, defaultValue = "0", updateMode = UpdateMode.IMMUTABLE, comment = "china region parent level id")
    private Long parentId;

    @Column(defaultValue = "0", nullable = false, comment = "china region population")
    private Integer population;
}
io.army.modelgen.ArmyMetaModelDomainProcessor generate static metamodel class
// Army static metamodel class

@Generated(value = "io.army.modelgen.ArmyMetaModelDomainProcessor",
        date = "2024-01-02 07:13:54.605524+08:00",
        comments = "china region")
@SuppressWarnings("unchecked")
public abstract class ChinaRegion_ {

    private ChinaRegion_() {
        throw new UnsupportedOperationException();
    }

    public static final ParentTableMeta<ChinaRegion<?>> T;

    /** Due to ChinaRegion&lt;?> contains type parameter(s) , army generate static CLASS for army session query api. */
    public static final Class<ChinaRegion<?>> CLASS = (Class<ChinaRegion<?>>) ((Class<?>) ChinaRegion.class);

    static {
        final ParentTableMeta<?> temp;
        temp = _TableMetaFactory.getParentTableMeta(ChinaRegion.class);
        T = (ParentTableMeta<ChinaRegion<?>>) temp;

        final int fieldSize = T.fieldList().size();
        if (fieldSize != 10) {
           throw _TableMetaFactory.tableFiledSizeError(ChinaRegion.class,fieldSize);
        }
    }

    /** Due to ChinaRegion&lt;?> contains type parameter(s) , army generate static constructor method for army session query api. */
    public static ChinaRegion<?> constructor() {
        return new ChinaRegion<>();
    }

    /*-------------------following table filed names-------------------*/

    /** {@link ChinaRegion#regionGdp } china region GDP */
    public static final String REGION_GDP = "regionGdp";

    /** {@link ChinaRegion#visible } visible for logic delete */
    public static final String VISIBLE = "visible";

    /** {@link ChinaRegion#regionType } @see io.army.example.bank.domain.user.RegionType */
    public static final String REGION_TYPE = "regionType";

    /** {@link ChinaRegion#createTime } create time */
    public static final String CREATE_TIME = "createTime";


    /** {@link ChinaRegion#name } china region name */
    public static final String NAME = "name";

    /** {@link ChinaRegion#updateTime } update time */
    public static final String UPDATE_TIME = "updateTime";

    /** {@link ChinaRegion#id } primary key */
    public static final String ID = "id";

    /** {@link ChinaRegion#version } version for optimistic lock */
    public static final String VERSION = "version";


    /** {@link ChinaRegion#parentId } china region parent level id */
    public static final String PARENT_ID = "parentId";

    /** {@link ChinaRegion#population } china region population */
    public static final String POPULATION = "population";



    /*-------------------following table filed metas-------------------*/

    /** {@link ChinaRegion#regionGdp } china region GDP */
    public static final FieldMeta<ChinaRegion<?>> regionGdp = T.getField(REGION_GDP);

    /** {@link ChinaRegion#visible } visible for logic delete */
    public static final FieldMeta<ChinaRegion<?>> visible = T.getField(VISIBLE);

    /** {@link ChinaRegion#regionType } @see io.army.example.bank.domain.user.RegionType */
    public static final FieldMeta<ChinaRegion<?>> regionType = T.getField(REGION_TYPE);

    /** {@link ChinaRegion#createTime } create time */
    public static final FieldMeta<ChinaRegion<?>> createTime = T.getField(CREATE_TIME);


    /** {@link ChinaRegion#name } china region name */
    public static final FieldMeta<ChinaRegion<?>> name = T.getField(NAME);

    /** {@link ChinaRegion#updateTime } update time */
    public static final FieldMeta<ChinaRegion<?>> updateTime = T.getField(UPDATE_TIME);

    /** {@link ChinaRegion#id } primary key */
    public static final PrimaryFieldMeta<ChinaRegion<?>> id = T.id();

    /** {@link ChinaRegion#version } version for optimistic lock */
    public static final FieldMeta<ChinaRegion<?>> version = T.getField(VERSION);


    /** {@link ChinaRegion#parentId } china region parent level id */
    public static final FieldMeta<ChinaRegion<?>> parentId = T.getField(PARENT_ID);

    /** {@link ChinaRegion#population } china region population */
    public static final FieldMeta<ChinaRegion<?>> population = T.getField(POPULATION);


} // ChinaRegion_
How to start test use case
public class HowToStartTests {

    private static DatabaseSessionFactory sessionFactory;

    @BeforeClass
    public void createSessionFactory() {
        final Map<String, Object> envMap = new HashMap<>();
        envMap.put(ArmyKey.DATABASE.name, Database.MySQL);
        envMap.put(ArmyKey.DIALECT.name, MySQLDialect.MySQL80);

        final DataSource dataSource;
        dataSource = createDataSource();
        sessionFactory = SyncFactoryBuilder.builder()
                .name("army-test")
                .packagesToScan(Collections.singletonList("io.army.example.bank.domain"))
                .datasource(dataSource)
                .environment(StandardEnvironment.from(envMap))
                // .fieldGeneratorFactory(new SimpleFieldGeneratorFactory())
                .build();
    }

    private DataSource createDataSource() {
        // do create datasource
        throw new UnsupportedOperationException();
    }

    @Test
    public void save() {
        final ChinaRegion<?> region;
        region = new ChinaRegion<>();
        region.setName("五指礁");

        try (SyncLocalSession session = sessionFactory.localSession()) {
            session.save(region);
        }
    }

    @Test
    public void insert() {
        final List<ChinaRegion<?>> regionList;
        regionList = this.createReginList();
        final Insert stmt;
        stmt = SQLs.singleInsert()  // standard criteria api
                //.literalMode(LiteralMode.LITERAL)
                .insertInto(ChinaRegion_.T)
                .defaultValue(ChinaRegion_.visible, SQLs::literal, Boolean.TRUE) // default value for each row
                .values(regionList)
                .asInsert(); // end statement

        try (SyncLocalSession session = sessionFactory.localSession()) {
            session.update(stmt);
        }
    }

    @Test
    public void update() {
        final BigDecimal addGdp = new BigDecimal("888.8");
        final Map<String, Object> map = new HashMap<>();
        map.put("firstId", (byte) 1);
        map.put("secondId", "3");

        final Update stmt;
        stmt = SQLs.singleUpdate()
                .update(ChinaRegion_.T, AS, "c")
                .set(ChinaRegion_.name, SQLs::param, "武侠江湖")
                .set(ChinaRegion_.regionGdp, SQLs::plusEqual, SQLs::param, addGdp)
                .where(ChinaRegion_.id::between, SQLs::literal, map.get("firstId"), AND, map.get("secondId"))
                .and(SQLs::bracket, ChinaRegion_.name.equal(SQLs::literal, "江湖"))
                .and(ChinaRegion_.regionGdp::plus, SQLs::param, addGdp, Expression::greaterEqual, BigDecimal.ZERO)
                .asUpdate();

        try (SyncLocalSession session = sessionFactory.localSession()) {
            session.update(stmt);
        }
    }


    @Test
    public void batchUpdate() {
        final BatchUpdate stmt;
        stmt = SQLs.batchSingleUpdate()
                .update(ChinaRegion_.T, AS, "p") // update only parent table field: ChinaRegion_.*
                .setSpace(ChinaRegion_.regionGdp, SQLs::plusEqual, SQLs::namedParam)
                .where(ChinaRegion_.id::equal, SQLs::namedParam)
                .and(ChinaRegion_.regionGdp::plus, SQLs::namedParam, Expression::greaterEqual, BigDecimal.ZERO) // test method infer
                .and(ChinaRegion_.regionGdp::plus, SQLs::namedParam, ChinaRegion_.REGION_GDP, Expression::greaterEqual, BigDecimal.ZERO) // test method infer
                .ifAnd(ChinaRegion_.regionGdp::plus, SQLs::namedParam, ChinaRegion_.REGION_GDP, Expression::greaterEqual, BigDecimal.ZERO) // test method infer
                .and(ChinaRegion_.version::equal, SQLs::param, "0")
                .asUpdate()
                .namedParamList(this.createProvinceList());

        try (SyncLocalSession session = sessionFactory.localSession()) {
            session.batchUpdate(stmt);
        }

    }


    @Test
    public void queryFields() {
        final Select stmt;
        stmt = SQLs.query()
                .select(ChinaRegion_.id, ChinaRegion_.name, ChinaRegion_.regionGdp)
                .from(ChinaRegion_.T, AS, "c")
                .where(ChinaRegion_.name.equal(SQLs::param, "曲境")) // bind parameter, output '?'
                .and(ChinaRegion_.createTime::less, SQLs::literal, LocalDateTime.now().minusDays(1)) // bind literal ,output literal
                .limit(SQLs::literal, 1) // bind literal ,output literal
                .asQuery();

        try (SyncLocalSession session = sessionFactory.localSession()) {
            session.queryObject(stmt, RowMaps::hashMap)  // io.army.util.RowMaps
                    .forEach(map -> LOG.debug("{}", map));
        }
    }


    @Test
    public void querySimpleDomain() {
        final Select stmt;
        stmt = SQLs.query()
                .select("c", PERIOD, Captcha_.T)
                .from(Captcha_.T, AS, "c")
                .where(Captcha_.requestNo.equal(SQLs::param, "3423423435435")) // bind parameter, output '?'
                .and(Captcha_.createTime::less, SQLs::literal, LocalDateTime.now().minusDays(1)) // bind literal ,output literal
                .limit(SQLs::literal, 1) // bind literal ,output literal
                .asQuery();

        try (SyncLocalSession session = sessionFactory.localSession()) {
            session.query(stmt, Captcha.class)
                    .forEach(c -> LOG.debug("{}", c.getCaptcha()));
        }
    }

    @Test
    public void queryGenericsDomain() {
        final Select stmt;
        stmt = SQLs.query()
                .select("c", PERIOD, ChinaRegion_.T)
                .from(ChinaRegion_.T, AS, "c")
                .where(ChinaRegion_.name.equal(SQLs::param, "曲境")) // bind parameter, output '?'
                .and(ChinaRegion_.createTime::less, SQLs::literal, LocalDateTime.now().minusDays(1)) // bind literal ,output literal
                .limit(SQLs::literal, 1) // bind literal ,output literal
                .asQuery();

        try (SyncLocalSession session = sessionFactory.localSession()) {
            session.queryObject(stmt, ChinaRegion_::constructor)
                    .forEach(c -> LOG.debug("{}", c.getName()));
        }
    }


    @Test
    public void localTransaction() {
        final List<ChinaRegion<?>> regionList;
        regionList = this.createReginList();
        final Insert stmt;
        stmt = SQLs.singleInsert() // standard criteria api
                //.literalMode(LiteralMode.LITERAL)
                .insertInto(ChinaRegion_.T)
                .defaultValue(ChinaRegion_.visible, SQLs::literal, Boolean.TRUE) // default value for each row
                .values(regionList)
                .asInsert(); // end statement

        try (SyncLocalSession session = sessionFactory.localSession()) {
            session.startTransaction();
            try {
                session.update(stmt);
                session.commit();
            } catch (Exception e) {
                session.rollback();
                throw e;
            }

        }
    }


    @Test
    public void subDomainInsertChild() {
        assert HistoryChinaRegion_.id.generatorType() == GeneratorType.POST;

        final List<ChinaProvince> provinceList;
        provinceList = this.createProvinceList();

        final Insert stmt;

        stmt = Postgres.singleInsert() // postgre dialect api
                .literalMode(LiteralMode.LITERAL)
                .with("parent").as(s -> s.literalMode(LiteralMode.LITERAL)
                        .insertInto(ChinaRegion_.T)
                        .values(provinceList)
                        .returning(ChinaRegion_.id)
                        .asReturningInsert()
                ).comma("parent_row_id").as(s -> s.select(ss -> ss.space(Postgres.rowNumber().over().as("rowNumber"))
                                        .comma(SQLs.refField("parent", ChinaRegion_.ID))
                                )
                                .from("parent")
                                .asQuery()
                )
                .space()
                .insertInto(ChinaProvince_.T)
                .defaultValue(ChinaProvince_.id, Postgres.scalarSubQuery()
                        .select(s -> s.space(SQLs.refField("p", ChinaRegion_.ID)))
                        .from("parent_row_id", AS, "p")
                        .where(SQLs.refField("p", "rowNumber")::equal, BATCH_NO_LITERAL)
                        .asQuery()
                )
                .values(provinceList)
                .asInsert();

        Assert.assertFalse(stmt instanceof _ReturningDml);

        try (SyncLocalSession session = sessionFactory.localSession()) {
            final long rows;
            rows = session.update(stmt);
            LOG.debug("{} rows : {}", session.name(), rows);
            Assert.assertEquals(rows, provinceList.size());
        }

    }


}

Alt text

About

One better way to write SQL in Java ; one better blocking SQL framework ; one better high-level database driver ; one better reactive SQL framework .

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages