easy-query

repository·main·Indexed 20 days ago

https://github.com/dromara/easy-query

A high-performance, lightweight Java/Kotlin ORM framework providing a type-safe API for database operations. It supports advanced features including database sharding, read-write separation, implicit complex query logic (joins, subqueries, grouping), and aggregate filtering with CASE WHEN expressions. Compatible with Spring Boot and Solon, it utilizes APT for proxy generation via @EntityProxy to enable fluent, type-safe queries.

Tokens
15K
Snippets
42
Records
44
Agent score
73%

What's inside easy-query

  1. Define Entity Proxies for strong-typed queries

    main

    To use the strong-typed API, your entities must implement ProxyEntityAvailable<Entity, EntityProxy>. You can use the @EntityProxy or @EntityFileProxy annotations. This allows you to use lambda expressions (e.g., o.id()) in queries instead of raw strings. The EntityProxy can be generated using the EasyQueryAssistant IDEA plugin.

    @Data
    @Table("t_topic")
    @EntityProxy
    public class Topic implements ProxyEntityAvailable<Topic, TopicProxy> {
      @Column(primaryKey = true)
      private String id;
      private Integer stars;
      private String title;
      private LocalDateTime createTime;
    }
  2. What are the five implicit features of easy-query?

    main

    easy-query provides five powerful implicit features that simplify complex SQL operations using type-safe expressions:

    1. Implicit Join: Automatically implements join queries for filtering, sorting, and retrieving results for OneToOne and ManyToOne relationships.
    2. Implicit Subquery: Automatically implements subqueries for filtering, sorting, and aggregate function results for OneToMany and ManyToMany relationships.
    3. Implicit Grouping: Optimizes multiple subqueries into a single grouped query for OneToMany and ManyToMany relationships, supporting filtering, sorting, and aggregation.
    4. Implicit Partition Grouping: Automatically handles filtering, sorting, and aggregation for the first or N-th data element in OneToMany and ManyToMany relationships.
    5. Implicit CASE WHEN Expression: Allows using the pattern property.aggregateFunction.filter (e.g., o.age().sum().filter(()->o.name().like("123"))) to generate CASE WHEN logic within queries.
  3. Define Entity Proxies with @EntityProxy

    main

    To enable easy-query's fluent API, annotate your data classes with @EntityProxy (or @EntityFileProxy). This allows the generation of proxy objects that provide type-safe access to columns. You can use the EasyQueryAssistant IDEA plugin to quickly generate the ProxyEntityAvailable interface implementation.

    @Data
    @Table("t_blog")
    @EntityProxy //or @EntityFileProxy
    public class BlogEntity extends BaseEntity implements ProxyEntityAvailable<BlogEntity , BlogEntityProxy>{
      private String title;
      private String content;
      // ... other fields
    }
  4. Initialize EasyQueryClient and EasyEntityQuery in Console mode

    main

    In a standalone console application, manually initialize a HikariDataSource, build an EasyQueryClient using the EasyQueryBootstrapper, and then create an EasyEntityQuery instance.

    //init DataSource
    HikariDataSource dataSource=new HikariDataSource();
    dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/easy-query-test?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true");
    dataSource.setUsername("root");
    dataSource.setPassword("root");
    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    dataSource.setMaximumPoolSize(20);
    
    //property api client
    EasyQueryClient easyQueryClient=EasyQueryBootstrapper.defaultBuilderConfiguration()
             .setDataSource(dataSource)
             .useDatabaseConfigure(new MySQLDatabaseConfiguration())
             .build();
    
    //entity query api
    EasyEntityQuery easyEntityQuery=new DefaultEasyEntityQuery(easyQueryClient);
  5. Install easy-query for Spring Boot

    main

    To use easy-query in a Spring Boot environment, add the sql-springboot-starter dependency to your pom.xml. Replace last-version with the current version of easy-query.

    <properties>
      <easy-query.version>last-version</easy-query.version>
    </properties>
    <dependency>
        <groupId>com.easy-query</groupId>
        <artifactId>sql-springboot-starter</artifactId>
        <version>${easy-query.version}</version>
    </dependency>
  6. Implement Data Source Sharding (分库)

    main

    Data source sharding (Database Sharding) distributes data across multiple different databases (DataSources) rather than just different tables.

    To implement data source sharding:

    1. Annotate your Entity: Use @Table with a shardingInitializer and mark the sharding key field with @ShardingDataSourceKey.
    2. Create a Sharding Initializer: Implement EntityShardingInitializer. Use builder.actualTableNameInit(initTables) where initTables is a map linking DataSource names (e.g., ds2020) to their respective table names.
    3. Create a Data Source Route: Extend AbstractDataSourceRoute and override getRouteFilter. This method must handle different ShardingOperatorEnum types (like EQUAL, GREATER_THAN, LESS_THAN) to return a predicate that filters which DataSources should be queried.

    Example of a paginated query across multiple data sources:

    // 1. Entity Definition
    @Data
    @Table(value = "t_topic_sharding_ds", shardingInitializer = DataSourceAndTableShardingInitializer.class)
    public class TopicShardingDataSource {
        @Column(primaryKey = true)
        private String id;
        @ShardingDataSourceKey
        private LocalDateTime createTime;
    }
    
    // 2. Data Source Initializer
    public class DataSourceShardingInitializer implements EntityShardingInitializer<TopicShardingDataSource> {
        @Override
        public void configure(ShardingEntityBuilder<TopicShardingDataSource> builder) {
            EntityMetadata entityMetadata = builder.getEntityMetadata();
            String tableName = entityMetadata.getTableName();
            List<String> tables = Collections.singletonList(tableName);
            LinkedHashMap<String, Collection<String>> initTables = new LinkedHashMap<String, Collection<String>>() {{ 
                put("ds2020", tables);
                put("ds2021", tables);
                put("ds2022", tables);
                put("ds2023", tables);
            }};
            builder.actualTableNameInit(initTables);
        }
    }
    
    // 3. Data Source Route
    public class TopicShardingDataSourceRoute extends AbstractDataSourceRoute<TopicShardingDataSource> {
        @Override
        protected RouteFunction<String> getRouteFilter(TableAvailable table, Object shardingValue, ShardingOperatorEnum shardingOperator, boolean withEntity) {
            LocalDateTime createTime = (LocalDateTime) shardingValue;
            String dataSource = "ds" + createTime.getYear();
            switch (shardingOperator){
                case EQUAL: return ds -> dataSource.compareToIgnoreCase(ds) == 0;
                case GREATER_THAN: return ds -> dataSource.compareToIgnoreCase(ds) <= 0;
                // ... other operators
                default: return t -> true;
            }
        }
    }
    
    // 4. Execution
    EasyPageResult<TopicShardingDataSource> pageResult = easyQuery.queryable(TopicShardingDataSource.class)
            .orderByAsc(o -> o.column(TopicShardingDataSource::getCreateTime))
            .toPageResult(1, 33);
  7. Implement Table Sharding

    main

    Table Sharding allows you to split a single logical table into multiple physical tables (e.g., by month).

    Configuration Steps:

    1. Annotate the Entity: Use @Table with the shardingInitializer attribute. Use @ShardingTableKey on the field used for routing (this does not have to be the primary key).
    2. Create a Sharding Initializer: Implement AbstractShardingMonthInitializer (or similar) to define the time range (getBeginTime, getEndTime) and optimize performance via configure.
    3. Create a Table Route: Implement AbstractMonthTableRoute to define how the sharding value (e.g., LocalDateTime) maps to a table.

    Example Implementation:

    @Table(value = "t_topic_sharding_time", shardingInitializer = TopicShardingTimeShardingInitializer.class)
    public class TopicShardingTime {
        @Column(primaryKey = true)
        private String id;
        @ShardingTableKey
        private LocalDateTime createTime;
    }
    
    public class TopicShardingTimeShardingInitializer extends AbstractShardingMonthInitializer<TopicShardingTime> {
        @Override
        protected LocalDateTime getBeginTime() { return LocalDateTime.of(2020, 1, 1, 1, 1); }
        @Override
        protected LocalDateTime getEndTime() { return LocalDateTime.of(2023, 5, 1, 0, 0); }
    }
    
    public class TopicShardingTimeTableRoute extends AbstractMonthTableRoute<TopicShardingTime> {
        @Override
        protected LocalDateTime convertLocalDateTime(Object shardingValue) { return (LocalDateTime) shardingValue; }
    }
    @Table(value = "t_topic_sharding_time", shardingInitializer = TopicShardingTimeShardingInitializer.class)
    public class TopicShardingTime {
        @Column(primaryKey = true)
        private String id;
        private Integer stars;
        private String title;
        @ShardingTableKey
        private LocalDateTime createTime;
    }
    
    public class TopicShardingTimeShardingInitializer extends AbstractShardingMonthInitializer<TopicShardingTime> {
        @Override
        protected LocalDateTime getBeginTime() {
            return LocalDateTime.of(2020, 1, 1, 1, 1);
        }
    
        @Override
        protected LocalDateTime getEndTime() {
            return LocalDateTime.of(2023, 5, 1, 0, 0);
        }
    }
    
    public class TopicShardingTimeTableRoute extends AbstractMonthTableRoute<TopicShardingTime> {
        @Override
        protected LocalDateTime convertLocalDateTime(Object shardingValue) {
            return (LocalDateTime) shardingValue;
        }
    }
  8. Install easy-query in Spring Boot

    main

    To use easy-query in a Spring Boot environment, add the sql-springboot-starter dependency to your pom.xml. This provides the necessary integration for automatic configuration within the Spring ecosystem.

    <properties>
      <easy-query.version>last-version</easy-query.version>
    </properties>
    <dependency>
        <groupId>com.easy-query</groupId>
        <artifactId>sql-springboot-starter</artifactId>
        <version>${easy-query.version}</version>
    </dependency>
  9. Initialize EasyQuery in Console mode

    main

    In a standalone application, manually initialize the EasyQueryClient using a DataSource and a database configuration object, then wrap it in a DefaultEasyQuery for strong-typed API access.

    // Initialize connection pool
    HikariDataSource dataSource = new HikariDataSource();
    dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/easy-query-test?...");
    dataSource.setUsername("root");
    dataSource.setPassword("root");
    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    dataSource.setMaximumPoolSize(20);
    
    // Non-strong-typed API
    EasyQueryClient easyQueryClient = EasyQueryBootstrapper.defaultBuilderConfiguration()
        .setDataSource(dataSource)
        .useDatabaseConfigure(new MySQLDatabaseConfiguration())
        .build();
    
    // Strong-typed API
    EasyQuery easyQuery = new DefaultEasyQuery(easyQueryClient);
  10. Install easy-query with Proxy support

    main

    To use the proxy-based API, add the @EntityProxy annotation to your entity classes and include the following dependencies in your Maven project. Note: You must run the project build (APT) to generate the proxy Java code.

    <properties>
      <easy-query.version>last-version</easy-query.version>
    </properties>
    <dependency>
      <groupId>com.easy-query</groupId>
      <artifactId>sql-api-proxy</artifactId>
      <version>${easy-query.version}</version>
    </dependency>
    <dependency>
      <groupId>com.easy-query</groupId>
      <artifactId>sql-mysql</artifactId>
      <version>${easy-query.version}</version>
    </dependency>
  11. Implement Database Sharding

    main

    Database Sharding distributes data across multiple data sources (databases).

    Configuration Steps:

    1. Annotate the Entity: Use @Table with a shardingInitializer and use @ShardingDataSourceKey on the routing field.
    2. Create a Sharding Initializer: Implement EntityShardingInitializer to map data sources to their respective table names using builder.actualTableNameInit(Map<String, Collection<String>>).
    3. Create a Data Source Route: Implement AbstractDataSourceRoute and override getRouteFilter. This method determines which data sources should be queried based on the shardingOperator (e.g., EQUAL, GREATER_THAN, LESS_THAN) and the shardingValue.

    Example Implementation:

    @Table(value = "t_topic_sharding_ds", shardingInitializer = DataSourceAndTableShardingInitializer.class)
    public class TopicShardingDataSource {
        @ShardingDataSourceKey
        private LocalDateTime createTime;
    }
    
    public class DataSourceShardingInitializer implements EntityShardingInitializer<TopicShardingDataSource> {
        @Override
        public void configure(ShardingEntityBuilder<TopicShardingDataSource> builder) {
            LinkedHashMap<String, Collection<String>> initTables = new LinkedHashMap<>() {{ 
                put("ds2020", Collections.singletonList("t_topic_sharding_ds")); 
                // ... other years
            }};
            builder.actualTableNameInit(initTables);
        }
    }
    
    public class TopicShardingDataSourceRoute extends AbstractDataSourceRoute<TopicShardingDataSource> {
        @Override
        protected RouteFunction<String> getRouteFilter(TableAvailable table, Object shardingValue, ShardingOperatorEnum shardingOperator, boolean withEntity) {
            LocalDateTime createTime = (LocalDateTime) shardingValue;
            String dataSource = "ds" + createTime.getYear();
            // Logic to return true/false for data source matching based on operator
            return ds -> ds.equals(dataSource); 
        }
    }
    @Table(value = "t_topic_sharding_ds", shardingInitializer = DataSourceAndTableShardingInitializer.class)
    public class TopicShardingDataSource {
        @Column(primaryKey = true)
        private String id;
        private Integer stars;
        private String title;
        @ShardingDataSourceKey
        private LocalDateTime createTime;
    }
    
    public class DataSourceShardingInitializer implements EntityShardingInitializer<TopicShardingDataSource> {
        @Override
        public void configure(ShardingEntityBuilder<TopicShardingDataSource> builder) {
            EntityMetadata entityMetadata = builder.getEntityMetadata();
            String tableName = entityMetadata.getTableName();
            List<String> tables = Collections.singletonList(tableName);
            LinkedHashMap<String, Collection<String>> initTables = new LinkedHashMap<>() {{ 
                put("ds2020", tables);
                put("ds2021", tables);
                put("ds2022", tables);
                put("ds2023", tables);
            }};
            builder.actualTableNameInit(initTables);
        }
    }
    
    public class TopicShardingDataSourceRoute extends AbstractDataSourceRoute<TopicShardingDataSource> {
        @Override
        protected RouteFunction<String> getRouteFilter(TableAvailable table, Object shardingValue, ShardingOperatorEnum shardingOperator, boolean withEntity) {
            LocalDateTime createTime = (LocalDateTime) shardingValue;
            String dataSource = "ds" + createTime.getYear();
            switch (shardingOperator){
                case EQUAL:
                    return ds-> dataSource.compareToIgnoreCase(ds)==0;
                // ... other cases
                default:return t->true;
            }
        }
    }
  12. Install easy-query for Console mode

    main

    For non-Spring Boot (Console) applications, you need to include the API, the proxy support (for strong-typed SQL via APT), and the specific database driver module.

    Note:

    • sql-api-proxy: Provides proxy mode support for strong-typed SQL using APT (non-lambda).
    • sql-api4j: Provides strong-typed Java syntax using lambda expressions.
    • sql-[database]: Required for database-specific behavior (e.g., sql-mysql).
    <properties>
      <easy-query.version>last-version</easy-query.version>
    </properties>
    <!-- Proxy mode support for APT strong-typed SQL -->
    <dependency>
    <groupId>com.easy-query</groupId>
    <artifactId>sql-api-proxy</artifactId>
    <version>${easy-query.version}</version>
    </dependency>
    <!-- Strong-typed Java syntax via lambda -->
    <dependency>
    <groupId>com.easy-query</groupId>
    <artifactId>sql-api4j</artifactId>
    <version>${easy-query.version}</version>
    </dependency>
    <!-- Database specific support (e.g., MySQL) -->
    <dependency>
    <groupId>com.easy-query</groupId>
    <artifactId>sql-mysql</artifactId>
    <version>${easy-query.version}</version>
    </dependency>