Java ORM framework recommendation for java development

1 Preface

ObjectiveSQL is a Java ORM framework. It is not only the application of Active Record mode in Java, but also provides a nearly perfect solution for complex SQL programming, making the organic combination of Java code and SQL statements, java training It changes the traditional SQL programming model (mainly string splicing programming model).

The ObjectiveSQL project is divided into two parts: one is that Maven relies on objective SQL or objectsql springboot during runtime, which mainly implements the basic ORM features and SQL programming model; the other is the IntelliJ IDEA plug-in, which is compatible with Java operator overloading and dynamic code prompts.

ObjectiveSQL mainly solves:

  • Dynamic code generation: automatically generate simple SQL programming code based on Domain Model, so that application system development only focuses on its own business characteristics and improves development efficiency
  • Programmable SQL: abstract the control primitives, predicates, functions and procedural logic in SQL into high-level types in Java and integrate them with Java, making SQL a real procedural and logical programming language, which can be encapsulated, reused and unit tested
  • Expression syntax consistency: equivalent replacement of Java syntax and SQL syntax, including mathematical calculation, function call, comparison and logical calculation expressions. Java expressions can be directly converted to SQL expressions.

2 dependent installation

2.1 installation of IntelliJ idea plug-in

Preferences/Settings -> Plugins -> Search with "ObjectiveSql" in market -> Install

2.2 Maven integration

For stand-alone applications, add the following code to dependencies:

<!-- In standalone -->
<dependency>
    <groupId>com.github.braisdom</groupId>
    <artifactId>objective-sql</artifactId>
    <version>{objsql.version}</version>
</dependency>
Copy code

For the Spring Boot integration project, add the following code to the dependencies:

<!-- In Spring Boot, you need add spring-jdbc dependency before -->
<dependency>
  <groupId>com.github.braisdom</groupId>
  <artifactId>objsql-springboot</artifactId>
  <version>{objsql.version}</version>
</dependency>
Copy code

For the latest version, please visit ObjectiveSQL, ObjSqlSpringBoot

2.3 Maven Compiler parameter configuration

Please add the following code to POM Under / node in XML:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    <configuration>
        <source>8</source>
        <target>8</target>
        <encoding>UTF-8</encoding>
        <compilerArgs>
            <arg>-Xplugin:JavaOO</arg>
        </compilerArgs>
        <annotationProcessorPaths>
            <path>
                <groupId>com.github.braisdom</groupId>
                <artifactId>objective-sql</artifactId>
                <version>${objsql.version}</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>
Copy code

3 database connection injection

3.1 independent application system

Take MySQL as an example, construct data connection acquisition logic based on ConnectionFactory and inject it into Databases.

private static class MySQLConnectionFactory implements ConnectionFactory {

    @Override
    public Connection getConnection(String dataSourceName) throws SQLException {
        try {
            String url = "jdbc:mysql://localhost:4406/objective_sql";
            String user = "root";
            String password = "******";
            return DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
}

Databases.installConnectionFactory(new MySQLConnectionFactory());
Copy code

The dataSourceName parameter in the getConnection method is only used in the scenario of multiple data sources. The getConnection method can return different database connections according to different dataSourceName, and this parameter can be ignored in other scenarios.

3.2 integrated Spring Boot

When the application system is developed based on the Spring Boot framework, there is no need to inject data sources manually. Please configure it according to the following methods:

spring:
  profiles:
    name: objective-sql-example
    active: development

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:4406/objective_sql
    username: root
    password: ******
    hikari:
      idle-timeout: 10000
      maximum-pool-size: 10
      minimum-idle: 5
      pool-name: Master

    # Configurations for multiple databases
    extensions:
      # The name of data source, which will match with @DomainModel definition 
      slave:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:4406/objective_sql
        username: root
        password: ******
        hikari:
          idle-timeout: 10000
          maximum-pool-size: 10
          minimum-idle: 5
          pool-name: Slave
 Copy code

The extensions tag needs to be configured only when there are multiple data sources, and the slave, as the data source name, should match the data source name defined in the DomainModel, or through the dynamic data source name in the domainmodeldescriptor.

4 simple SQL Programming Guide

The simple SQL programming provided by ObjectiveSQL is mainly aimed at the use of SQL related to single tables, and provides a convenient development experience for the development of application systems through dynamically generated Java API s.

4.1 naming convention

4.1. 1 class name and table name

By default, ObjectiveSQL converts Java elements to database elements in the form of humps and underscores. Examples are as follows:

1) Java is defined as follows:

class Member {
        private String memberNo;
    private String name;
}
Copy code

2) The database table is defined as follows:

create table members (
        member_no varchar not null,
    name varchar
);
Copy code

Class name: the name of Member in the database is members, and the column name of field name memberNo is member_no, and the field name name does not change

4.1. 1 associated object

1) Java is defined as follows:

class Member {
        private String memberNo;
    private String name;

    @Relation(relationType = RelationType.HAS_MANY)
    private List<Order> orders;
}

class Order {
        private String no;
    private Long memberId;

    @Relation(relationType = RelationType.BELONGS_TO)
    private Member member;
}
Copy code

2) The database table is defined as follows:

create table members (
        member_no varchar not null,
    name varchar
);

create table members (
        member_no varchar not null,
    member_id int(10) not null,
    name varchar
);
Copy code

Several key features can be seen from the above structure definition:

  • Used to carry has_ The instance variable members of many association object is converted from type to complex number, and BELONGS_TO and HAS_ONE is singular
  • There is an instance variable memberId corresponding to the foreign key in the Order class, and there is also a member in the table_ ID corresponds to it
  • Other rules are consistent with the rules of class and table conversion

Note: when converting all class names to complex ones, follow the rules of English, for example, person corresponds to pepole

4.2 domain model definition

@DomainModel
public class Member {
    @Size(min = 5, max = 20)
    private String no;

    @Queryable
    private String name;
    private Integer gender;
    private String mobile;

    @Transient
    private String otherInfo;

    @Relation(relationType = RelationType.HAS_MANY)
    private List<Order> orders;
}
Copy code

ObjectiveSQL will automatically generate basic SQL programming related methods and SQL abstract model definitions according to the above model definitions

4.3 data query

Member.countAll();
Member.count("name = ?", "braisdom");

Member.queryByPrimaryKey(1);
Member.queryFirst("id > ?", 1);
Member.query("id > ?", 1);
Member.queryAll();
Copy code

4.4 data update

Member.create(newMember);
Member.create(newMember, true); // Create a member without validating
Member.create(Member.newInstanceFrom(memberHash));
Member.create(new Member[]{newMember1, newMember2, newMember3}, false);

Member.update(1L, newMember, true); // Update a member by primary key and skip validationg
Member.update("name = ?", "name = ?", newName, oldName);

Member.destroy(1L); // Delete a member by primary key
Member.destroy("name = ?", "Mary");
Copy code

4.5 affairs

4.5. 1 Annotation based transactions

// The method will be executed in a database thransaction
@Transactional
public static void makeOrder(Order order, OrderLine... orderLines) throws SQLException {
  Order.create(order, false);
  OrderLine.create(orderLines, false);
}
Copy code

4.5. 2 manual transaction management

// Transaction executing manually
Databases.executeTransactionally(((connection, sqlExecutor) -> {
      Member.update(1L, newMember, true);
      Member.update("name = ?", "name = ?", newName, oldName);
    return null;
}));
Copy code

4.6 associated object query

Member.queryAll(Member.HAS_MANY_ORDERS);
Member.queryFirst("id > ?", Member.HAS_MANY_ORDERS, 1);
Member.query("id > ?", Member.HAS_MANY_ORDERS, 1);

Member.queryByPrimaryKey(1, Member.HAS_MANY_ORDERS);
Member.queryByName("braisdom", Member.HAS_MANY_ORDERS);
Copy code

Member in the above code HAS_ MANY_ The orders attribute is automatically generated by ObjectiveSQL. In special cases, it can be based on COM github. braisdom. objsql. relation. Relationship defines the logic for building association relationships.

4.7 paging query

// Create a Page instance with current page and page size
Page page = Page.create(0, 10);
PagedList<Member> members = Member.pagedQueryAll(page, Member.HAS_MANY_ORDERS);
PagedList<Member> members = Member.pagedQuery(page, "name = ?", "braisdom");
Copy code

4.8 Query interface programming

Query query = Member.createQuery();
query.project("name").groupBy("name").having("COUNT(*) > 0").orderBy("name DESC");

List<Member> members = query.execute(Member.HAS_MANY_ORDERS);

// Paged querying with querying dynamically
Paginator paginator = Databases.getPaginator();
Page page = Page.create(0, 10);
PagedList<Member> pagedMembers = paginator
                      .paginate(page, query, Member.class, Member.HAS_MANY_ORDERS);
Copy code

Grouping and sorting in SQL need to be completed through the Query interface. At the same time, the Query interface can also perform paging and associated object Query.

4.9 Validation

ObjectiveSQL Validation internally integrates Jakarta Bean Validation

Please refer to: beanvalidation.org/

4.9. 1. Manually call the validate method

Member newMember = new Member()
        .setNo("100")
        .setName("Pamela")
        .setGender(1)
        .setMobile("15011112222");

// Violations occurred in field 'no'
Validator.Violation[] violations = newMember.validate();
Copy code

4.9. 2. validate when creating an object

Member newMember = new Member()
        .setNo("100000")
        .setName("Pamela")
        .setGender(1)
        .setMobile("15011112222");

Member.create(newMember); 
Member.create(newMember, true); // Skip validation
 Copy code

4.10 custom SQL

Member.execute("DELETE FROM members WHERE name = ?", "Pamela");
Copy code

5 complex SQL Programming Guide

The complex SQL programming provided by ObjectiveSQL is actually an abstraction and modeling of SQL syntax. It interacts with each other in the form of Java API, so that complex SQL no longer appears in Java in the form of string, so that dynamic SQL becomes clear and easy to understand. Different business systems can also re abstract and model their own business based on ObjectiveSQL, Realize the reuse of SQL logic.

5.1 JOIN query

5.1. 1 implicit Join

Member.Table member = Member.asTable();
Order.Table order = Order.asTable();

Select select = new Select();

select.project(member.no, member.name, count().as("order_count"))
        .from(member, order)
        .where(member.id.eq(order.memberId))
        .groupBy(member.no, member.name);

List<Member> members = select.execute(Member.class);
Copy code
SELECT `T0`.`NO` , `T0`.`name` , COUNT(*) AS `order_count`
FROM `members` AS `T0`, `orders` AS `T1`
WHERE (`T0`.`id` = `T1`.`member_id` )
GROUP BY `T0`.`NO` , `T0`.`name`
Copy code

5.1. 2 explicit Join

Member.Table member = Member.asTable();
Order.Table order = Order.asTable();

Select select = new Select();

select.project(member.no, member.name, count().as("order_count"))
        .from(member)
        .leftOuterJoin(order, order.memberId.eq(member.id))
        .groupBy(member.no, member.name);

List<Member> members = select.execute(Member.class);
Copy code
SELECT `T0`.`NO` , `T0`.`name` , COUNT(*) AS `order_count`
FROM `members` AS `T0`
LEFT OUTER JOIN `orders` AS `T1` ON (`T1`.`member_id` = `T0`.`id` )
GROUP BY `T0`.`NO` , `T0`.`name`
Copy code

5.2 paging query

Member.Table member = Member.asTable();
Order.Table order = Order.asTable();

Paginator<Member> paginator = Databases.getPaginator();
Page page = Page.create(0, 20);

Select select = new Select();

select.project(member.no, member.name, count().as("order_count"))
        .from(member, order)
        .where(member.id.eq(order.memberId))
        .groupBy(member.no, member.name);

PagedList<Member> members = paginator.paginate(page, select, Member.class);
Copy code
-- Counting SQL
SELECT COUNT(*) AS count_
FROM (
    SELECT
        `T0`.`NO`,
        `T0`.`name`,
        COUNT(*) AS `order_count`
    FROM `members` AS `T0`, `orders` AS `T1`
    WHERE (`T0`.`id` = `T1`.`member_id`)
    GROUP BY `T0`.`NO`, `T0`.`name`
) T
 Copy code
-- Querying SQL
SELECT `T0`.`NO`, `T0`.`name`, COUNT(*) AS `order_count`
FROM `members` AS `T0`, `orders` AS `T1`
WHERE (`T0`.`id` = `T1`.`member_id`)
GROUP BY `T0`.`NO`, `T0`.`name`
LIMIT 0, 20
 Copy code

5.3 complex expression query

ObjectiveSQL enables Expression to participate in the calculation of various operators through operator re domain technology, which makes Java code easy to understand, rather than calculation through various operator methods. ObjectiveSQL expressions cannot match the SQL Expression completely during calculation. By default, all expressions can perform arithmetic operations, and complete reminders cannot be given in IntelliJ IDEA. For example, JoinExpression can also perform arithmetic operations. At this time, reminders of syntax errors will not appear in IntelliJ IDEA, However, unsupported arithmetalexception will be thrown during operation, which is a subclass of RuntimeException.

Order.Table orderTable = Order.asTable();
Select select = new Select();

select.project((sum(orderTable.amount) / sum(orderTable.quantity) * 100).as("unit_amount"))
        .from(orderTable)
        .where(orderTable.quantity > 30 &&
                orderTable.salesAt.between("2020-05-01 00:00:00", "2020-05-02 23:59:59"))
        .groupBy(orderTable.memberId);

List<Order> orders = select.execute(Order.class);
Copy code
SELECT ((((SUM(`T0`.`amount` ) / SUM(`T0`.`quantity` ) )) * 100)) AS unit_amount
FROM `orders` AS `T0`
WHERE ((`T0`.`quantity` > 30)
    AND `T0`.`sales_at` BETWEEN '2020-05-01 00:00:00' AND '2020-05-02 23:59:59' )
GROUP BY `T0`.`member_id`
Copy code

5.4 dynamic query

The so-called dynamic query actually means that the construction process of the expression changes with the presence or absence of parameters. Based on this use scenario, ObjectiveSQL designs an eternal logical expression. The eternal expression is a clever design in the program, which makes the code logic clearer, even if all parameters are not assigned, The whole expression will also have a permanent expression to ensure the normal operation of the final SQL statement.

String[] filteredNo = {"202000001", "202000002", "202000003"};
int filteredQuantity = 0;

Order.Table orderTable = Order.asTable();
Select select = new Select();
LogicalExpression eternalExpression = new EternalExpression();

if(filteredNo.length > 0) {
    eternalExpression = eternalExpression.and(orderTable.no.in(filteredNo));
}

if(filteredQuantity != 0) {
    eternalExpression = eternalExpression.and(orderTable > filteredQuantity);
}

select.project((sum(orderTable.amount) / sum(orderTable.quantity) * 100).as("unit_amount"))
        .from(orderTable)
        .where(eternalExpression)
        .groupBy(orderTable.memberId);

List<Order> orders = select.execute(Order.class);
Copy code
SELECT ((((SUM(`T0`.`amount` ) / SUM(`T0`.`quantity` ) )) * 100)) AS unit_amount
FROM `orders` AS `T0`
WHERE ((1 = 1) AND `T0`.`NO` IN ('202000001', '202000002', '202000003') )
GROUP BY `T0`.`member_id`
Copy code

6 advanced use

6.1 log integration

Because ObjectiveSQL cannot determine which log framework the application system uses, ObjectiveSQL does not integrate any third-party log framework. Confirm to use JDK's own log framework. If the application system needs to use its own log framework and inject ObjectiveSQL after the system is started, please integrate in the following way (taking Slf4j as an example).

6.1. 1. Loggerfactory extension implementation

public class ObjLoggerFactoryImpl implements LoggerFactory {

    private class ObjLoggerImpl implements Logger {

        private final org.slf4j.Logger logger;

        public ObjLoggerImpl(org.slf4j.Logger logger) {
            this.logger = logger;
        }

        @Override
        public void debug(long elapsedTime, String sql, Object[] params) {
            logger.debug(createLogContent(elapsedTime, sql, params));
        }

        @Override
        public void info(long elapsedTime, String sql, Object[] params) {
            logger.info(createLogContent(elapsedTime, sql, params));
        }

        @Override
        public void error(String message, Throwable throwable) {
            logger.error(message, throwable);
        }

        private String createLogContent(long elapsedTime, String sql, Object[] params) {
            String[] paramStrings = Arrays.stream(params)
                    .map(param -> String.valueOf(param)).toArray(String[]::new);
            String paramString = String.join(",", paramStrings);
            return String.format("[%dms] %s, with: [%s]",
                    elapsedTime, sql, String.join(",",
                    paramString.length() > 100 ? StringUtil
                            .truncate(paramString, 99) : paramString));
        }
    }

    @Override
    public Logger create(Class<?> clazz) {
        org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(clazz);
        return new ObjLoggerImpl(logger);
    }
}
Copy code

6.1. 2 ordinary application injection mode

public class Application {

    public static void main(String[] args) {
        Databases.installLoggerFactory(new ObjLoggerFactoryImpl());
        // others
    }
}
Copy code

6.1.3 Spring Boot application injection mode

@SpringBootApplication
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(Application.class);
        springApplication.addListeners(new ApplicationListener<ApplicationReadyEvent>() {

            @Override
            public void onApplicationEvent(ApplicationReadyEvent event) {
                Databases.installLoggerFactory(new ObjLoggerFactoryImpl());
            }
        });
        springApplication.run(args);
    }
}
Copy code

6.2 object cache based on SQL statement

In the application system, data with weak timeliness will be cached. Usually, the data will be cached in Redis. For these features, the SQLExecutor interface of ObjectiveSQL can be extended for easy implementation.

6.2. 1. Sqlexecutor extension implementation

public class CacheableSQLExecutor<T> extends DefaultSQLExecutor<T> {

    private static final List<Class<? extends Serializable>> CACHEABLE_CLASSES =
            Arrays.asList(new Class[]{Member.class});
    private static final Integer CACHED_OBJECT_EXPIRED = 60;
    private static final String KEY_SHA = "SHA";

    private Jedis jedis = new Jedis("localhost", 6379);
    private MessageDigest messageDigest;

    public CacheableSQLExecutor() {
        try {
            messageDigest = MessageDigest.getInstance(KEY_SHA);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }

    @Override
    public List<T> query(Connection connection, String sql,
                         TableRowAdapter tableRowAdapter, Object... params) 
      throws SQLException {
        Class<?> domainClass = tableRowAdapter.getDomainModelClass();

        if (CACHEABLE_CLASSES.contains(domainClass)) {
            if(!Serializable.class.isAssignableFrom(domainClass)) {
                throw new IllegalArgumentException(String
                                                   .format("The %s cannot be serialized"));
            }

            messageDigest.update(sql.getBytes());

            String hashedSqlId = new BigInteger(messageDigest.digest()).toString(64);
            byte[] rawObjects = jedis.get(hashedSqlId.getBytes());

            if (rawObjects != null) {
                return (List<T>) SerializationUtils.deserialize(rawObjects);
            } else {
                List<T> objects = super.query(connection, sql, tableRowAdapter, params);
                byte[] encodedObjects = SerializationUtils.serialize(objects);
                SetParams expiredParams = SetParams.setParams().ex(CACHED_OBJECT_EXPIRED);

                jedis.set(hashedSqlId.getBytes(), encodedObjects, expiredParams);

                return objects;
            }
        }
        return super.query(connection, sql, tableRowAdapter, params);
    }
}
Copy code

6.2. 2 injection mode

public class Application {

    public static void main(String[] args) {
        Databases.installSqlExecutor(new CacheableSQLExecutor());
        // others
    }
}
Copy code

The injection method of Spring Boot is the same as that of LogFactory

6.3 ColumnTransition extension

ColumnTransition is an extension interface for data type conversion provided by ObjectiveSQL. For the detailed definition of this interface, please refer to: ColumnTransition Java, take the date form as an example to introduce the extension of ColumnTransition.

public class SqlDateTimeTransition<T> implements ColumnTransition<T> {

    @Override
    public Object sinking(DatabaseMetaData databaseMetaData, T object,
                          TableRowAdapter tableRowDescriptor, 
                          String fieldName, FieldValue fieldValue) 
      throws SQLException {
        String databaseName = databaseMetaData.getDatabaseProductName();
        if (fieldValue != null && fieldValue.getValue() != null) {
            if (SQLite.equals(databaseName) || Oracle.equals(databaseName)) {
                return fieldValue;
            } else if (PostgreSQL.equals(databaseName)) {
                if (fieldValue.getValue() instanceof Timestamp) {
                    return fieldValue.getValue();
                } else if (fieldValue.getValue() instanceof Long) {
                    Instant value = Instant.ofEpochMilli((Long) fieldValue.getValue());
                    return Timestamp.from(value);
                } else {
                    return Timestamp.valueOf(String.valueOf(fieldValue.getValue()));
                }
            } else {
                return fieldValue;
            }
        }
        return null;
    }

    @Override
    public Object rising(DatabaseMetaData databaseMetaData, 
                         ResultSetMetaData resultSetMetaData,
                         T object, TableRowAdapter tableRowDescriptor, 
                         String columnName, Object columnValue) throws SQLException {
        String databaseName = databaseMetaData.getDatabaseProductName();
        try {
            if (columnValue != null) {
                if (SQLite.equals(databaseName)) {
                   Instant value = Instant
                     .ofEpochMilli(Long.valueOf(String.valueOf(columnValue)))
                    return Timestamp.from(value);
                } else {
                    return columnValue;
                }
            }
        } catch (DateTimeParseException ex) {
            String message = String.format("Invalid raw DataTime of '%s' from database: %s",
                    columnName, columnValue);
            throw new IllegalArgumentException(message, ex);
        }
        return null;
    }
}
Copy code

The singing method is to convert the value in Java into a value acceptable to the database, and rising is to convert the value in the database into a value acceptable to Java.


Author: IT Xiaoshang
Link: https://juejin.cn/post/7041016816293904420
Source: rare earth Nuggets
The copyright belongs to the author. For commercial reprint, please contact the author for authorization, and for non-commercial reprint, please indicate the source.

 

Keywords: Java data structure Back-end

Added by Brakanjan on Wed, 15 Dec 2021 21:13:51 +0200