Introducing Spring Data JDBC

Original author: Jens Schauder
  • Transfer

In the upcoming release of Spring Data, code-named Lovelace, we are going to include a new module: Spring Data JDBC .


The idea behind Spring Data JDBC is to provide access to relational databases without using all the complexity of JPA .


JPA offers such functions as lazy loading , caching and change tracking (dirty tracking). Despite the fact that these features are very cool, if, of course, you really need them, they can make the understanding of data access logic much more difficult.


Mechanism of lazy loading can suddenly fulfill resource-intensive requests, or even completely fall with the exception. Caching can get in your way when you decide to compare two versions of an entity, and together with tracking changes, will prevent you from understanding - at what point will all the database operations actually be performed?


Spring Data JDBC focuses on a much simpler model . There will be no caching, change tracking, or lazy loading. Instead, SQL queries will be executed if and only if you call the repository method. The returned result will be fully loaded into memory after the method is executed. There will be no "session" mechanism or proxy objects for entities. And all this should make Spring Data JDBC more simple and understandable data access tools.


Of course, such a simplified approach translates into a number of limitations, which we will discuss in the following posts. The upcoming release is the very first version of the library, we have many plans and plans that we want to implement, but we have to postpone them to give you the opportunity to start using Spring Data JDBC as soon as possible.


Example


First, we need to define an entity:


classCustomer{
    @Id
    Long id;
    String firstName;
    LocalDate dob;
}

Please note that we do not define any getters or setters. You can of course add them if you want. In essence, the only requirement for an entity is to have the field annotated with an annotation Id(but precisely org.springframework.data.annotation.Id, not javax.persistence one).

Next, you need to define a repository. The easiest way to do this is to expand the interface CrudRepository.


interfaceCustomerRepositoryextendsCrudRepository<Customer, Long> {}

Finally, you need to configure the ApplicationContextimplementation of this interface to be created automatically:


@Configuration@EnableJdbcRepositories (1)
publicclassCustomerConfigextendsJdbcConfiguration{ (2)
    @BeanNamedParameterJdbcOperations operations(){ (3)
        returnnew NamedParameterJdbcTemplate(dataSource());
    }
    @BeanPlatformTransactionManager transactionManager(){ (4)
        returnnew DataSourceTransactionManager(dataSource());
    }
    @BeanDataSource dataSource(){ (5)
        returnnew EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("create-customer-schema.sql")
                .build();
    }
}

Let's analyze the configuration in more detail.


  1. EnableJdbcRepositoriesactivates the automatic creation of repositories. In order for this to work, you need to provide a few extra bins, for which you will need the rest of our configuration class.
  2. Since the configuration class extends JdbcConfiguration, several bins will be added to the context automatically. You can also override them if you want to change the behavior of Spring Data JDBC. But in this example, we leave the default behavior.
  3. A very important component is NamedParameterJdbcOperationsthat which is used to perform database queries.
  4. The transaction manager, strictly speaking, is not required. But without it, there will be no transaction support, and few people will like it, right?
  5. Spring Data JDBC does not use DataSourcedirectly, but TransactionManagerand NamedParameterJdbcOperationrequire its existence in the context, so we determine the right bin.

That's all it takes to get started with Spring Data JDBC. Now we will write a test to see how it all works:


@RunWith(SpringRunner.class)
@Transactional@ContextConfiguration(classes = CustomerConfig.class)
publicclassCustomerRepositoryTest{
    @Autowired CustomerRepository customerRepo;
    @TestpublicvoidcreateSimpleCustomer(){
        Customer customer = new Customer();
        customer.dob = LocalDate.of(1904, 5, 14);
        customer.firstName = "Albert";
        Customer saved = customerRepo.save(customer);
        assertThat(saved.id).isNotNull();
        saved.firstName = "Hans Albert";
        customerRepo.save(saved);
        Optional<Customer> reloaded = customerRepo.findById(saved.id);
        assertThat(reloaded).isNotEmpty();
        assertThat(reloaded.get().firstName).isEqualTo("Hans Albert");
    }
}

annotation @Query


Only with standard methods of the CRUD repository CrudRepositoryyou will not get far from the class . We deliberately decided to postpone the automatic generation of the query - a popular feature of Spring Data, when the SQL query is generated based on the name of the method - for future releases. And for the time being, you can simply use the familiar annotation @Queryto indicate exactly which SQL query should be executed.


@Query("select id, first_name, dob from customer where upper(first_name) like '%' || upper(:name) || '%' ")
List<Customer> findByName(@Param("name") String name);

If you want to change or delete the data in the request, you can add annotation to the method @Modifying.


Let's write a test to see how our new method works.


@TestpublicvoidfindByName(){
    Customer customer = new Customer();
    customer.dob = LocalDate.of(1904, 5, 14);
    customer.firstName = "Albert";
    Customer saved = customerRepo.save(customer);
    assertThat(saved.id).isNotNull();
    customer.id= null; (1)
    customer.firstName = "Bertram";
    customerRepo.save(customer);
    customer.id= null;
    customer.firstName = "Beth";
    customerRepo.save(customer);
    assertThat(customerRepo.findByName("bert")).hasSize(2); (2)
}

  1. Since connection between Java objects and a record in the database only by field Idand type, then setting Idin nulland saving this object will create a new record.
  2. We use case-sensitive like in the request, and therefore we find “Albert” and “Bertram”, but not “Beth”.

In conclusion


Of course, much more can be said about Spring Data JDBC, and we will definitely tell in the following articles.


In the meantime, you can study the code for the examples , the documentation , and, of course, the source code . If you have any questions, feel free to ask them on StackOverflow . And if you find a bug or want to request a new feature, please create a ticket .


Also popular now: