Doma Database Access Framework

repository·master·Indexed 19 days ago

https://github.com/domaframework/doma

A database access framework for Java and Kotlin that emphasizes compile-time safety through annotation processing. Doma features a type-safe Criteria API (Query DSL), 'two-way SQL' templates with conditional logic and expansion directives, and built-in support for entity associations via @AggregateStrategy and @AssociationLinker. Doma 3 requires Java 17 or higher.

Tokens
86.3K
Snippets
257
Records
302
Agent score
67%

What's inside Doma

  1. What is Doma?

    master

    Doma is a database access framework for Java designed to provide type-safety and compile-time validation. Its core strengths include:

    • Compile-time validation: Uses annotation processing to check and generate source code during compilation.
    • Entity Associations: Supports defining relationships between entities.
    • Type-safe Criteria API: Provides a way to build queries with type safety.
    • Two-way SQL: Uses SQL templates that allow for bidirectional mapping between SQL and Java objects.
    • Zero Dependencies: Runs independently without requiring other libraries.
  2. Key Features of quarkus-doma

    master

    The quarkus-doma extension provides several automated features for Quarkus users:

    • Hot reloading: Automatically detects and reloads SQL and Script files during development mode.
    • Automatic bean registration: Automatically registers all DAO beans in the Quarkus CDI container.
    • Automatic SQL execution on startup: Automatically executes the file specified by quarkus.doma.sql-load-script (defaults to import.sql) to initialize the database.
    • Native image support: Automatically handles reflective classes and resources required for GraalVM native images without extra configuration.
  3. What are Basic classes in Doma

    master
    In Doma, "Basic classes" are Java types that can be mapped directly to database column types. This includes primitive types (except char), their corresponding wrapper classes, enums, byte[], java.lang.String, java.lang.Object, java.math.BigDecimal, java.math.BigInteger, and various temporal/SQL types.
  4. Define and use Scopes for reusable query conditions

    master

    Scopes allow you to define and reuse common query conditions.

    1. Define a Scope Class: Create a class where methods are annotated with @Scope. These methods typically return a Consumer<WhereDeclaration> or Consumer<OrderByNameDeclaration>.
    2. Register the Scope: Add the scope class to the scopes element of the @Metamodel annotation on your @Entity.
    3. Use the Scope: The metamodel (e.g., Department_) will now have methods corresponding to your scope definitions.

    You can combine a scope with other conditions using the andThen method.

    // 1. Define the scope
    public class DepartmentScope {
        @Scope
        public Consumer<WhereDeclaration> onlyTokyo(Department_ d) {
            return c -> c.eq(d.location, "Tokyo");
        }
    }
    
    // 2. Register in Entity
    @Entity(metamodel = @Metamodel(scopes = { DepartmentScope.class }))
    public class Department { ... }
    
    // 3. Use it
    Department_ d = new Department_();
    List<Department> list = entityql.from(d).where(d.onlyTokyo()).fetch();
    
    // Combine with other conditions
    List<Department> list = entityql.from(d).where(d.onlyTokyo().andThen(c -> c.gt(d.departmentNo, 50))).fetch();
  5. Configure Doma with Multiple Datasources

    master

    To use Doma with multiple named datasources, prefix the Doma configuration properties with the datasource name. For example, if you have a datasource named inventory, use quarkus.doma.inventory.<property>.

    To inject the specific Doma resources associated with a named datasource, use the @DataSource("name") qualifier from io.quarkus.agroal.DataSource.

    # default datasource
    quarkus.datasource.db-kind=h2
    quarkus.datasource.username=username-default
    quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:default
    
    # inventory datasource
    quarkus.datasource.inventory.db-kind=h2
    quarkus.datasource.inventory.username=username2
    quarkus.datasource.inventory.jdbc.url=jdbc:h2:tcp://localhost/mem:inventory
    
    # Doma's configuration bound to the default datasource
    quarkus.doma.dialect=h2
    
    # Doma's configuration bound to the inventory datasource
    quarkus.doma.inventory.dialect=h2
    quarkus.doma.inventory.batch-size=10
    @Inject
    Config defaultConfig;
    
    @Inject
    @DataSource("inventory")
    Config inventoryConfig;
    
    @Inject
    @DataSource("inventory")
    Entityql inventoryEntityql;
    
    @Inject
    @DataSource("inventory")
    NativeSql inventoryNativeSql;
  6. Define search conditions in @Select

    master

    Search conditions are defined using method parameters. Doma supports several parameter types:

    • Basic classes and Domain classes: Can be passed as null.
    • Arbitrary types: Use a dot . in the bind variable directive to access fields or invoke methods (e.g., /* employee.name */).
    • java.util.Optional: Can contain Basic, Domain, or arbitrary types.
    • java.util.Iterable: Used for mapping to the SQL IN clause.
    • Optional primitives: OptionalInt, OptionalLong, OptionalDouble.

    Note: For all types other than Basic or Domain classes, the argument must not be null.

    -- Using basic classes
    select * from employee where employee_name = /* name */'hoge'
    
    -- Using arbitrary types (accessing fields/methods)
    select * from employee where employee_name = /* employee.name */'hoge' and salary > /* employee.getSalary() */100
    
    -- Mapping to an IN clause
    select * from employee where employee_name in /* names */('aaa','bbb','ccc')
  7. Use nested and optional embeddable classes

    master

    Doma supports complex composition of embeddable structures:

    • Nested Embeddables: An @Embeddable class can contain other @Embeddable classes as fields. All fields in the hierarchy are flattened into the parent entity's table.
    • Optional Embeddables: You can wrap an embeddable field in java.util.Optional.
      • If the Optional is empty, all corresponding database columns for that embeddable will be null.
      • If all database columns for an embeddable are null, the Optional field will be returned as Optional.empty().
    @Embeddable
    public class Address {
        String street;
        String city;
    }
    
    @Embeddable
    public class ContactInfo {
        String email;
        Address address; // Nested embeddable
    }
    
    @Entity
    public class Customer {
        @Id
        Integer id;
        Optional<ContactInfo> contactInfo; // Optional nested embeddable
    }
  8. Distinguish between SQL comments and Doma directives

    master

    Doma uses specific syntax to distinguish between standard SQL comments and template directives. Understanding this is crucial to avoid accidental directive execution or broken SQL.

    Single-line comments

    Any string starting with -- is always treated as a standard single-line comment and is never interpreted as a directive.

    Multi-line comments vs. Directives

    Multi-line comments start with /*. Doma determines if /* is a directive or a comment based on the character immediately following it:

    • It is a Multi-line Comment if the character following /* is NOT a valid Java identifier start character and is NOT one of %, #, @, ", or '.
      • Examples: /**...*/, /*+...*/, /*=...*/, /*;...*/.
    • It is a Directive if the character following /* is a valid Java identifier start character or one of the special directive characters.
      • Examples: /* ...*/, /*a...*/, /*$...*/, /*@...*/, /*"...*/, /*'...*/, /*#...*/, /*%...*/.

    Recommendation: Always use /**...*/ for multi-line comments to ensure they are clearly distinguishable from directives.

  9. Define an Entity class

    master

    An entity class in Doma corresponds to a database table or a query result set. You define an entity by annotating a class with @Entity.

    Key characteristics:

    • Inheritance: An entity class can inherit from another entity class.
    • Records: You can use Java record types as entities. When using a record, the entity is automatically treated as immutable, even if the immutable property of @Entity is not explicitly set to true.
    • Subclasses: Entity subclasses inherit the parent's configuration, such as naming conventions and listeners.
    @Entity
    public class Employee {
        //...
    }
    
    // Inheritance
    @Entity
    public class SkilledEmployee extends Employee {
        //...
    }
    
    // Using Records (automatically immutable)
    @Entity
    public record Employee(@Id Integer id, String name) {
    }
  10. Configure optimistic concurrency control in @Delete

    master

    Doma supports optimistic concurrency control during delete operations if the following conditions are met:

    • The entity class in the parameter has a property annotated with @Version.
    • The ignoreVersion property in the @Delete annotation is false.

    When enabled, the version number is included in the WHERE clause along with the identifier. If the delete count is 0, an OptimisticLockException is thrown.

    Configuration Options

    • ignoreVersion = true: The version number is not included in the delete condition. OptimisticLockException is not thrown even if no rows are deleted.
    • suppressOptimisticLockException = true: The version number is included in the delete condition, but OptimisticLockException is not thrown even if the delete count is 0.
    // Version is ignored in the WHERE clause
    @Delete(ignoreVersion = true)
    int delete(Employee employee);
    
    // Version is included, but no exception is thrown if 0 rows deleted
    @Delete(suppressOptimisticLockException = true)
    int delete(Employee employee);
  11. How the Unified Criteria API works

    master

    The Unified Criteria API provides a type-safe interface for executing queries by integrating Entityql and NativeSql DSLs. It relies on generated metamodel classes to ensure type safety during query construction.

    To enable this, you must annotate your entity classes with @Entity(metamodel = @Metamodel). Doma's annotation processor will then generate metamodel classes (e.g., Employee_ for an Employee entity) which are used as entry points for building queries.

    Key components:

    • Metamodel Classes: Generated classes used to reference entity properties in a type-safe manner.
    • Query DSL: The primary interface for building and executing queries.
    @Entity(metamodel = @Metamodel)
    public class Employee {
      @Id private Integer employeeId;
      // ...
    }
    // Generates Employee_ metamodel class
  12. Define a Data Access Object (DAO) interface

    master

    A Data Access Object (DAO) is an interface annotated with @Dao that provides structured access to your database. Doma's annotation processor automatically generates the implementation classes for these interfaces during compilation.

    Note that a single DAO interface does not need to map to a single entity; one DAO can manage multiple different entity classes and operations.

    @Dao
    public interface MyDao {
    
        @Select
        Employee selectEmployeeById(int id);
    
        @Select
        Department selectDepartmentByName(String name);
    
        @Update
        int updateAddress(Address address);
    }