Bean Searcher

repository·dev·Indexed 23 days ago

https://github.com/troyzhxu/bean-searcher

A read-only ORM for Java designed for complex list retrieval, acting as a 'GraphQL for list retrieval' without a special protocol. It enables developers to implement multi-condition filtering, sorting, pagination, and statistics with minimal code using annotations like @SearchBean, @DbField, and @Export. The library supports integration with Solon (3.9, 4.0) and SpringBoot (2.7, 3.5) frameworks, and provides built-in CSV data export capabilities via BeanExporter.

Tokens
65.3K
Snippets
172
Records
266
Agent score
79%

What's inside bean-searcher

  1. What is Bean Searcher?

    dev

    Bean Searcher is a Java declarative search framework designed to simplify complex list retrieval. It allows clients to freely specify return fields, filter conditions, sort rules, and pagination via standard HTTP parameters in a single request.

    Key Characteristics

    • Declarative Search: Entities define the search boundaries, while client-provided parameters drive the query logic.
    • Zero-Annotation Search: Single-table entities can be made searchable without any annotations.
    • Framework Agnostic: It does not depend on a specific Web framework (e.g., Spring Boot, Solon, Grails, JFinal) or a specific ORM (e.g., MyBatis, Hibernate).
    • Protocol Agnostic: It works with standard HTTP parameters (GET, POST, form submissions) and does not require a dedicated protocol like GraphQL.
  2. Overview of Bean Searcher

    dev

    Bean Searcher is a Java declarative search framework designed for efficient list retrieval. It is often described as the 'GraphQL of REST APIs' because it allows clients to drive queries—specifying which fields to return, which operators to use for filtering, and how to sort—without requiring the backend to write custom logic for every combination of parameters.

    Key features include:

    • Client-Driven Queries: Frontend controls filtering, sorting, pagination, and statistics via parameters.
    • Native Multi-Table Joins: Automatically generates JOIN SQL based on entity relationships.
    • Zero Intrusion: Works alongside existing ORMs like MyBatis or JPA; the ORM handles CRUD, while Bean Searcher handles complex list queries.
    • High Performance: Generates SQL directly to avoid ORM wrapper overhead.
    • Secure by Default: Includes built-in protection against SQL injection, oversized pagination, and deep-offset throttling.
  3. Performance comparison of Bean Searcher

    dev

    Bean Searcher is designed primarily for developer efficiency, but it maintains high performance comparable to or exceeding other popular ORM frameworks.

    Key Performance Findings:

    • 5 ~ 10x faster than Spring Data JDBC
    • 2 ~ 3x faster than Spring Data JPA
    • 2 ~ 3x faster than MyBatis Plus
    • 1 ~ 2x faster than native MyBatis

    Important Note on Testing Environment: These benchmarks were conducted using an embedded H2 database. Because H2 has very low SQL execution latency, the overhead of the ORM framework becomes the primary bottleneck, making the performance differences more apparent.

    In production environments using traditional remote databases like MySQL, the performance gap between frameworks will likely be smaller. However, if you are using in-memory databases or high-performance databases like ClickHouse, the performance advantages of Bean Searcher will be highly significant.

  4. Use Bean Searcher Exporter for streaming data export

    dev
    Starting from v4.5, Bean Searcher includes an Exporter component that enables memory-efficient, streaming data export from a SearchBean directly to a file (CSV by default). It handles large datasets by fetching data in batches, applying transformations, and writing to an output stream without loading the entire dataset into memory. It includes built-in concurrency control and adaptive database load management.
  5. Understand the Frontend Project Structure

    dev

    The project is built using Vue 3.5 (Composition API), Vite 6, TypeScript 5.7, and Ant Design Vue 4.2. Key directories include:

    • src/api/: Contains API request logic using native fetch and query parameter construction.
    • src/components/: Reusable UI components like DataTable.vue (using a-table) and FilterCard.vue (search forms).
    • src/types/: TypeScript definitions and constants for operators and column definitions.
    • src/styles/: Global CSS styles.
  6. What is the Bean Searcher Label system?

    dev

    The Label system is a result-enhancement component introduced in v4.4. It automatically fills specified fields in a SearchBean with human-readable labels based on ID values, enum values, or other field data. This process is often called dictionary translation.

    It is designed to resolve foreign-key lookups and enum conversions without requiring complex SQL JOINs or manual post-processing in your application code. It is particularly useful for:

    • Cross-Database Lookups in Microservices: When the source data and the reference data reside in different databases/services, making SQL JOINs impossible.
    • Dictionary Table Optimization: Avoiding repeated JOINs on small dictionary tables by caching them in memory and using the label system instead.
    • Enum Fields: Providing human-readable string representations for enum fields to the front end.
  7. Understand Field Parameters and Derivation Rules

    dev

    Field parameters are used for filtering query results and are derived from the Java field names in your entity class (not the database column names).

    For a field named name, Bean Searcher automatically derives the following parameter patterns using a hyphen (-) as the default separator:

    • name-{n}: The nth parameter value (e.g., name-0, name-1).
    • name: Equivalent to name-0 (the 0th parameter value).
    • name-op: The [field operator](#Field Operators) for the field (e.g., name-eq).
    • name-ic: A flag indicating whether to ignore case (e.g., name-ic=true).

    Note: If you change the separator via configuration, these patterns will update accordingly (e.g., name_op if the separator is _).

    public class User {
        private String name;
        // Omit other..
    }
  8. Ignore fields using static and transient modifiers

    dev

    Bean Searcher automatically ignores any fields in an entity class that are declared with the static or transient keywords. This is useful for constants or fields that should not participate in database mapping or search operations.

    public class Address {
        public static String SUZHOU = "Suzhou City"; // Automatically ignored
        private String city;    // Not ignored
        private String street;  // Not ignored
        private transient fullAddress;          // Automatically ignored
        // Getter Setter ...
    }
  9. Relationship between Bean Searcher and ORMs

    dev

    Bean Searcher is not an ORM. It is a complementary framework designed to fill the gap in complex list retrieval that ORMs like MyBatis or Hibernate often leave behind.

    FeatureBean SearcherHibernateMyBatis
    PositioningDeclarative Search FrameworkFully automatic ORMSemi-automatic ORM
    Entity MappingSupports mapping to multiple tablesNot supportedNot supported
    Field OperatorsDynamic (client-driven)StaticStatic
    CRUDRead-only (R)CRUDCRUD
    RelationshipComplementary coexistence

    Bean Searcher handles only database queries (Read operations) and can coexist with any existing ORM in your project.

  10. Define Field Attributes in a SearchBean

    dev

    Field attributes are Java fields in your retrieval entity class that map to database table fields. They are used to carry query results in list queries and to generate WHERE or HAVING conditions based on field parameters.

    By default, if you use @SearchBean(autoMapTo = "table_alias"), fields not annotated with @DbField are automatically mapped to that table alias. You can use @DbField("column_name") to explicitly map a field to a specific database column or a joined table column.

  11. Compare Bean Searcher vs MyBatis Implementation Effort

    dev

    This record highlights the reduction in boilerplate code when using Bean Searcher compared to a manual MyBatis implementation for common search tasks (filtering, sorting, pagination, statistics, and export).

    Bean Searcher Implementation

    Requires only a single line in the controller and annotations on the entity class:

    @GetMapping("/index")
    public SearchResult<User> index() {
        // Combined search, sorting, pagination, and statistics in one line
        return beanSearcher.search(User.class, User::getAge);
    }

    Entity configuration uses annotations like @SearchBean, @DbField, @LabelFor, and @Export to handle multi-table mapping, enum labels, and export formatting.

    MyBatis Implementation Requirements

    To achieve the same functionality, MyBatis requires manual implementation of:

    • Parameter Parsing: Manually parsing suffixes like -op or -ic (e.g., in UserController.buildQuery()).
    • Dynamic SQL: Writing complex <if>/<choose> logic in XML for filtering, joins, and sorting.
    • Pagination: Manually calculating and appending LIMIT and OFFSET.
    • Counting: Implementing a separate count query.
    • Statistics: Implementing separate aggregation queries (e.g., SUM(age)).
    • Enum Labels: Implementing TypeHandler and manual getter methods for human-readable labels.
    • CSV Export: Manually implementing streaming and batching logic.
    @GetMapping("/index")
    public SearchResult<User> index() {
        // 组合检索、排序、分页、统计 全在这一句
        return beanSearcher.search(User.class, User::getAge);
    }
  12. Define a SearchBean

    dev

    A SearchBean is an entity class that maps to a database table (or multiple tables). In Bean Searcher v3.x, you can often omit annotations. If no annotations are present, Bean Searcher treats the class as a single-table entity where field names map to database column names by default.

    Example entity:

    public class User {
        private Long id;
        private String name;
        private int age;
        // Getters and Setters...
    }
    public class User {             // By default, it maps to the user table.
    
        private Long id;            // By default, it maps to the id field.
        private String name;        // By default, it maps to the name field.
        private int age;           // By default, it maps to the age field.
    
        // Getter and Setter ...
    }