Skip to main content

spring_boot_starter_data 0.6.2 copy "spring_boot_starter_data: ^0.6.2" to clipboard
spring_boot_starter_data: ^0.6.2 copied to clipboard

Spring Data for spring_boot_dart: entities, repositories, derived and @Query methods, transactions, optimistic and pessimistic locks; no driver inside.

0.6.2 #

Changes #

  • The defaults of spring.sql.migration.locations (resources:db/migration) and spring.sql.init.*-locations (optional:resources:schema.sql, ...) use resources:; classpath: still works with a warning (see spring_core_dart).

0.6.1 #

Changes #

  • Pageable and Sort controller parameters (with spring_boot_starter_web): the glue the generator writes (template lib/src/web/paging_argument_resolver.dart.tmpl, the bean pagingArgumentResolvers, a WebMvcConfigurer that registers a HandlerMethodArgumentResolver) fills them from ?page=&size=&sort=prop,desc&sort=other as Spring Data Web does, with spring.data.web.pageable.default-page-size (20), max-page-size (2000), one-indexed-parameters, page-parameter, size-parameter, prefix, qualifier-delimiter and spring.data.web.sort.sort-parameter; @PageableDefault and @SortDefault on the parameter, @Qualifier for several. The logic is WebPaging / WebPagingProperties (no web types). The size is cut at the maximum, a negative or non-numeric page is 0, a size below one is the default; two unqualified Pageable parameters stop the start; an unknown sort property is the existing 400.
  • A returned Page<T> (PageImpl.toJson) is written as Spring Data 3.3's stable JSON: content and page: {size, number, totalElements, totalPages}. It could not be written as JSON before.
  • rollbackFor and noRollbackFor follow Spring's RuleBasedTransactionAttribute: when both match, the entry closest to the exception's class decides (noRollbackFor: [Hard] beats rollbackFor: [Soft] for a Hard extends Soft); a tie goes to rollbackFor. The generator writes each listed type's depth in the hierarchy next to its type test (BeanModule.typeDepths, BeanIntrospector.typeDepth); a module without depths keeps the old rule (rollbackFor wins). The difference is gone from "Compared with Spring" and from the differential test's known differences. Regenerate the .spring.dart files.
  • InvalidDataArgumentException (an InvalidDataAccessApiUsageException): a value of the caller the database cannot take, never in its message. A NUL character in a text argument, a list over the bound-parameter limit (a derived In, the PostgreSQL driver) and a LIKE pattern PostgreSQL rejects (22025, 22021) throw it instead of the plain exception, and the web glue answers it with a 400 Invalid request value (it was a 500); other InvalidDataAccessApiUsageExceptions stay a 500.

Fixes #

  • Case folding on SQLite: ignoreCase, ...ContainingIgnoreCase, ...StartingWithIgnoreCase and In...IgnoreCase fold every letter (ДЮНА finds Дюна, ä finds Ä), as the in-memory store and PostgreSQL do; the driver registers Dart's toLowerCase as spring_lower when a connection opens, also in the worker isolate. A test of Cyrillic and German titles now runs on the three stores.
  • A derived query's text argument ending in \, holding \% or \\ is matched literally on every store (tests on the three stores). In a @Query, PostgreSQL's 22025 (a LIKE pattern ending with its escape) is an InvalidDataAccessApiUsageException that says so, not a DataIntegrityViolationException; 22021 likewise.
  • A NUL character in a text argument is an InvalidDataAccessApiUsageException raised before the statement: for a derived query on every store (SQLite's LIKE stopped reading a pattern at it), for any text parameter on PostgreSQL (a @Query, a save). It was PostgreSQL's 22021, a 500 in the web glue (which maps only its few exceptions, so it stays a 500: validate request text).
  • A derived In / NotIn of more than 65 535 values no longer fails on PostgreSQL: the list is bound as one array parameter (= any($1) / <> all($1), null semantics unchanged). On SQLite a list past its limit of 32 766 values is an InvalidDataAccessApiUsageException that says so; a @Query with more than 65 535 values gets that message on PostgreSQL before it is sent. SqlDialect gains maxBoundParameters and arrayMembership.
  • @DataTest: @Transactional(transactionManager: 'name') on a test method, a @Nested group or the test class chooses the transaction manager of that test (the generator passes it to runInTestTransaction), over @DataTest(transactionManager:). Regenerate the .test.spring.dart files.

0.6.0 #

First release: Spring Data for Dart, on Spring Data JDBC's model (an aggregate is loaded and saved whole; other aggregates are referenced by id; no persistence context, no lazy loading, no proxies of entities). The package has no database driver inside: the application lists sqlite3 or postgres and the generator writes the glue.

Entities and repositories #

  • Entities: @Table, @Id, @Column, @Transient, @Version (optimistic locking: OptimisticLockingFailureException on a conflict), @CreatedDate, @LastModifiedDate, @CreatedBy, @LastModifiedBy (@EnableDataAuditing, AuditorAware), @MappedCollection children saved and deleted with their root, NamingStrategy. Default values of constructor parameters and fields are written into the DEFAULT clauses of created schemas.
  • References between aggregates: @Reference(Author) int authorId next to AggregateReference<Author, int> (the id type is checked against the target's @Id; a field of an entity type without @MappedCollection is refused with a hint), foreign keys and an index for every reference in created schemas, @OnDelete(OnDeleteAction.noAction / restrict / cascade / setNull / setDefault) (setDefault writes no default yet: the column becomes null), @Unique() and @UniqueConstraint([...], name:).
  • Repositories: an interface extending DataRepository (CrudRepository, ListCrudRepository, PagingAndSortingRepository, ListPagingAndSortingRepository) is implemented by the generator and is a bean, no annotation needed; @UseStore and @UseDataSource choose its store and database. The generated class extends SimpleRepository (the CRUD, sorting and paging methods) and writes one call on RepositoryOperations for each method the interface declares.
  • Derived queries (findTop5ByTitleContainingIgnoreCaseOrderByYearDesc: find, read, get, query, search, stream, count, exists, delete, remove, Distinct, Top, First, And, Or, IgnoreCase, OrderBy, every keyword) are parsed and checked by the build: a wrong property is a build error naming the interface, with the reason and a suggestion (No property 'titel' found for type 'Book'; did you mean 'title'?), and so is a method that is neither CRUD nor derived or a signature that does not fit.
  • Sort, SortOrder, Pageable, PageRequest, Page, Slice, Spring's DataAccessException hierarchy (with ConcurrencyFailureException as the root of the version, lock, deadlock and serialization failures, the type retry code catches).
  • @Query with named parameters, #{#entityName}, ${property}, DTO results, Page and Slice (with a countQuery), @Modifying, rowMapper and resultSetExtractor; values are always bound, never written into the SQL. {Book} and {Book.authorId} write the quoted table and column of an entity and its property through the mapping.
  • @Query is checked by the build, in the one error of its repository (Invalid repository X:): the method against its annotation (Future or Stream, Pageable and Sort, @Modifying, the mapper functions, @Lock, countQuery, the result type), the text (:name against the parameters, no plain ?), the entity placeholders, and misspelled tables and columns when a known name is close (b.titel: no column titel in books, did you mean 'title'?). The build knows the entities and what the migrations and init scripts of lib/resources/ create; it says nothing when unsure.
  • The :#{...} expressions (SpEL) of a @Query are compiled to Dart by the build when it can (QueryMethod.expressions), so a typo is a build error and nothing is interpreted at run time; what the compiler leaves alone (T(...), new, selection, matches, instanceof, ...) is interpreted when the query runs.
  • Entities are described by the generator as compact EntityIntrospections (properties, annotations, defaults, constructor role, and the functions get, create and write), registered as an EntityIntrospections bean; RelationalMappingContext is built from them. A class a @Query returns is mapped by EntityRowMapper; an @Introspected class goes through DataClassRowMapper, and a class described without a part that is used fails with an IntrospectionPartError that says to add @Introspected().

Transactions #

  • @Transactional on bean methods with Spring's seven propagations, isolation, timeout, read-only, rollbackFor / noRollbackFor (every exception rolls back by default: Dart has no checked exceptions), transactionManager:; TransactionTemplate, PlatformTransactionManager, AbstractPlatformTransactionManager, TransactionSynchronization. A @Transactional method must return a Future.
  • The current transaction lives in a Zone: it follows await, two concurrent requests never share one, requiresNew and notSupported set aside only their own store's transaction, and work started without await that outlives its transaction fails instead of running outside it.
  • @Lock(LockMode.pessimisticWrite / pessimisticRead, timeout:, skipLocked:) on derived find methods and findById, inside a transaction only. Repository writes run in a transaction: saveAll and an aggregate with its children are atomic.
  • The in-memory store has transactions too: read committed, row locks with deadlock detection and a lock timeout (spring.data.memory.lock-timeout, 10s), savepoints, read-only transactions refusing writes. It enforces references and unique keys exactly as SQLite with foreign_keys = ON does, so a test that passes in memory does not fail in production.
  • Compared with Spring on PostgreSQL: tool/data_reference (a Spring Boot 4.1 application with Spring Data JDBC and real @Transactional services) and test/transaction_matrix run the same 304 scenarios (the propagation matrix; rollback rules; read only; dirty read, non-repeatable read, phantom, lost update and write skew at five isolation levels, a deadlock; FOR UPDATE, FOR SHARE, NOWAIT, SKIP LOCKED, lock timeouts, @Version races; timeouts), two transactions side by side, and the CI job "Data compared with Spring Data" compares the outcomes. The deliberate differences are listed in the README ("Compared with Spring").

SQL databases #

  • spring.datasource.* and spring.datasources.<name>.* make a pooled DataSource (spring.datasource.pool.*), a DataSourceTransactionManager, JdbcTemplate, NamedParameterJdbcTemplate and JdbcClient beans for the primary database and for each named one (qualified, with fallbacks); repositories use the jdbc store once a primary data source exists. The driver is chosen by the URL's scheme among the JdbcDriver beans the generator writes for the driver packages the application lists.
  • SQLite through the application's sqlite3 package: transactions, savepoints, one statement at a time per connection, error translation (duplicate key, constraint, busy, wrong SQL), spring.datasource.sqlite.busy-timeout and base-directory. spring.datasource.sqlite.isolate=true opens each connection inside a worker isolate, so synchronous calls no longer hold the caller's isolate (Flutter's UI, a server's event loop); transactions, streams and errors behave as on the caller's isolate, and a worker that dies fails the pending statements with a SqlError (connectionLost) instead of hanging.
  • PostgreSQL through the application's postgres package: spring.datasource.url=postgresql://user:password@host:5432/db?sslmode=require (also jdbc:postgresql://…); sslmode (default prefer, verify-ca and verify-full check the certificate and host name), connect_timeout, application_name and currentSchema from the URL or spring.datasource.postgresql.*, any other parameter an error. Values are bound ($1, $2), a generated id is BIGINT GENERATED BY DEFAULT AS IDENTITY read back with RETURNING, every isolation level (read uncommitted runs as read committed) and READ ONLY, savepoints, statement timeouts cancelled on the server, transactional DDL, an advisory lock for the migrations of several instances. @Lock is FOR UPDATE / FOR SHARE with NOWAIT, SKIP LOCKED and SET LOCAL lock_timeout. Errors by SQL state (23505 DuplicateKeyException, 40001 CannotSerializeTransactionException, 40P01 DeadlockLoserDataAccessException, 55P03 CannotAcquireLockException, 57014 QueryTimeoutException, wrong SQL, lost connections...) with the server's message, never a bound value, the DETAIL line or a password.
  • The schema is ready before any use: the data source holds back its first connection until the migrations are applied and the entities checked, so no repository, JdbcTemplate, runner or @PostConstruct sees an unmigrated database. spring.sql.init.* scripts, migrations (lib/resources/db/migration/V<n>__<name>.sql, R__<name>.sql, spring.sql.migration.*, history in schema_history, checksums, one instance migrates while the others wait), spring.data.jdbc.schema = validate (default; also foreign keys and unique keys) / create / create-drop / none. The names of Flyway's settings (spring.flyway.*) are not used; they stop the start and name spring.sql.migration.*.
  • The JDBC layer: PooledDataSource, JdbcTemplate, NamedParameterJdbcTemplate (:name, lists expanded for IN), JdbcClient, RowMapper, DataClassRowMapper, ResultSetExtractor; JdbcConnection.update and the templates' updateWithKeyHolder take an optional keyColumn (PostgreSQL's returned key; SQLite ignores it).
  • spring.jdbc.template.query-timeout, spring.data.jdbc.show-sql, format-sql, show-parameters, slow-query-threshold; SQL logs after Spring's loggers (spring.jdbc.core.JdbcTemplate at debug, bound values at trace in StatementCreatorUtils, masked for names that look secret), slow statements at warn.
  • Startup failures are explained (APPLICATION FAILED TO START): a URL no driver handles (which package to add), two drivers for one scheme, a failed migration, a schema that does not fit the entities, a database that cannot be reached.
  • In an application that also depends on spring_boot_starter_web, the generator adds a HandlerExceptionResolver (the bean dataAccessExceptionResolver, replaced by a bean of that name): OptimisticLockingFailureException and DuplicateKeyException are answered with a 409 and EmptyResultDataAccessException with a 404, as a ProblemDetail with a fixed detail (never the exception's message); other data errors stay a 500.

Testing #

  • @DataTest (Spring's @DataJdbcTest): a test class that runs on the repositories of the application only (its data beans and the starter's auto-configuration, not the services, controllers, runners or other starters). A configured SQL database is replaced by an in-memory SQLite one on a pool of one connection (replaceDatabase: false keeps it), with the migrations or scripts applied or, without them, the tables created from the entities. Each test, with its @BeforeEach and @AfterEach methods, runs in a transaction rolled back afterwards; @Commit() and @Rollback(false) commit, on a test, a class or a @Nested group; @DataTest(include: [...]) adds beans by type and @DataTest(transactionManager: 'name') runs the tests in that one manager's transaction. package:spring_boot_starter_data/testing.dart has DataTestSetup, DataTestSlice, TestDatabase and runInTestTransaction for the generated test file and for tests written as functions (the starter does not depend on spring_boot_starter_test). Documented in docs/testing.md, section 19.
  • @DataTest(replaceDatabase: false) with a @Container() @ServiceConnection() PostgresContainer (spring_boot_starter_test) runs the repositories on that PostgreSQL with the migrations applied and every test rolled back.
  • The starter's own tests tagged postgres run on SPRING_TEST_POSTGRES_URL when it is set (CI's service container) and otherwise on a PostgresContainer each test file starts, skipped without Docker.

Security (the 0.6.0 security check) #

  • Driver settings are read through the environment: SPRING_DATASOURCE_POSTGRESQL_SSLMODE=verify-full (and every other postgresql.* and sqlite.* key, for the primary and the named data sources) is no longer ignored. Each driver lists its keys (JdbcDriver.propertyNames); a name the driver does not know is still refused, in a file, an argument or a variable.
  • Values bound by save, derived queries, delete and the batch of NamedParameterJdbcTemplate carry the column they are bound to, so the trace log (and show-parameters at info) masks password, token, secret, key... values as it does a named @Query parameter (BoundParameters); a value bound without a name is masked at info and shown at trace.
  • A Sort or Pageable of a @Query is checked against what the result is mapped from: an entity's properties and columns, a DTO's properties (sorted by their snake-case columns) and the entity's; an unknown name is a PropertyReferenceException (400 through the web glue), not a database error. A single column, a Map or a rowMapper result keeps the quoted identifier, and the database decides.
  • A password (or any secret-looking parameter: sslpassword, token...) in the query string of the URL is masked in the pool's log lines and in every message about a URL. A name counts as secret when it contains password, passwd, passphrase, secret, credential, token, apikey, privatekey, accesskey, signingkey or encryptionkey (case and separators ignored: sslpassword, dbPassword, apitoken, clientsecret, privatekey), or has the word pwd or key; keyword, monkey, tokenizer and secretary do not.
  • A sort property from a request is a PropertyReferenceException (an InvalidDataAccessApiUsageException) whose message escapes the name (a line break cannot forge a log line); the web glue answers it, and an InvalidPagingRequestException, with a 400 and a fixed detail, logged at debug.
  • PageRequest.of refuses a page and size whose offset would pass 2^53 - 1 (it used to wrap around to another page), Direction.fromString throws the same exception, and the stores refuse a negative offset instead of dropping it.
  • @Version int? version is new only when null (Spring's rule for Long); a non-nullable int is still new at 0. A row stored at version 0 can be updated.
  • sslmode=prefer logs a warning, once for each database, when it falls back to plain text.
  • A synchronous runApplication(args) with a SQL data source fails with a report that says to use await runApplicationAsync(args) (the schema is set up asynchronously).
0
likes
160
points
--
downloads

Documentation

API reference

Publisher

verified publisherfarum-azula.org

Weekly Downloads

Spring Data for spring_boot_dart: entities, repositories, derived and @Query methods, transactions, optimistic and pessimistic locks; no driver inside.

Repository (GitHub)
View/report issues

Topics

#data #repository #sql #spring

License

Apache-2.0 (license)

Dependencies

spring_boot_dart, spring_boot_starter_logging

More

Packages that depend on spring_boot_starter_data