Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Spring Query Interfaces in CUBA

DZone's Guide to

Spring Query Interfaces in CUBA

Wondering how to add query interfaces in CUBA?

· Java Zone ·
Free Resource

Secure your Java app or API service quickly and easily with Okta's user authentication and authorization libraries. Developer accounts are free forever. Try Okta Instead.

Image title

Rationale

Developers usually don’t like to change their coding habits. When I started working with CUBA, I didn’t need to learn a lot of new things, creating applications was a pretty smooth process. One of the things that I ought to rediscover was working with data.

In Spring, there are several libraries that you can use for working with data, and one of the most popular is spring-data-jpa, which allows developers to avoid writing SQL or JPQL in most cases. You just need to specify an interface with methods that have special names and Spring will generate and execute a query for you.

For example, this is an interface with a method to count all customers with the given last name:

interface CustomerRepository extends CrudRepository<Customer, Long> {
  long countByLastName(String lastName);
}


You can inject this interface to your services and call this method where needed.

CUBA provides a lot of features out-of-the-box for data manipulation like partially loaded entities and sophisticated data security subsystem that restrict access to attributes or table rows. And, all those features come with an API that is a bit different from well-known Spring Data or JPA/Hibernate.

So why don't we have query interfaces in CUBA and is it possible to add them?

Working With Data in CUBA

There are three main classes in CUBA’s API for working with data: DataStoreEntityManager, and  DataManager.

DataStore abstraction provides an API to deal with persistent storage like RDBMS, file system, or cloud storage. It allows you to perform basic operations with data; however, it is not recommended to work with DataStore directly unless you’re developing a custom persistent storage or in a need for very special access to the underlying storage.

EntityManager is mostly a copy of a well-known JPA EntityManager, but it has additional methods to work with CUBA views, soft deletion, and methods to handle CUBA queries. As a CUBA developer, you may rarely need this class in your day-to-day work, but in cases when you need to overcome CUBA’s security restrictions.

The next facility, DataManager, is the main class to work with data in CUBA. It provides an API for data manipulation and supports CUBA security model including attribute- and row-level security. When you select data, DataManager modifies it implicitly. For example, in case of a relational data store, it updates “select” clause to exclude restricted attributes and appends “where” condition(s) that filter out database rows that should not be visible to a current user. This security-awareness is just amazing, while development you don't need to remember what security filters should be applied in each query.

Here is a diagram of CUBA classes interaction for fetching data from an RDBMS when DataManager is used.

Image title

With DataManager, you can select entities (as well as entity hierarchies using CUBA views) relatively easily. The simplest query may look like this:

dataManager.load(Customer.class).list()


 DataManager will take care about filtering out “soft-deleted” records, restricted attributes, and entities as well as creating a transaction.

But when it comes to queries with complex “where” conditions, you need to write a JPQL statement.

public Long countByLastName(String lastName){

   LoadContext<Person> loadContext = LoadContext.create(Person.class);

   loadContext
      .setQueryString("select c from sample$Customer c where c.lastName = :lastName")
      .setParameter("lastName", lastName);

   return dataManager.getCount(loadContext);
}


As you can see, you need to pass the JPQL statement to the DataManager for execution. In the CUBA API, you should define JPQL as a string (Criteria API is not supported yet). It is a readable and clear definition of a query, but it might be a bit challenging to debug if something goes wrong. Also, JPQL strings cannot be verified by a compiler during application build or by the Spring Framework during context initialization.

Compare it to this Spring JPA code snippet:

interface CustomerRepository extends CrudRepository<Customer, Long> {
  long countByLastName(String lastName);
}


It is three times shorter and does not include any strings. In addition to this, the method  countByLastName is verified at deploy stage. If you make a typo in the method’s name, like  countByLastNomethere will be an error:

Caused by: org.springframework.data.mapping.PropertyReferenceException: No property LastNome found for type Customer!

Since CUBA is built over the Spring Framework, you can add the spring-data-jpa to your CUBA project as a library and use this feature. The only issue — Spring’s query interfaces use the JPA EntityManager under the hood, so queries will be processed neither by CUBA’s EntityManager nor by the DataManager. Therefore, to add query interfaces to CUBA in a proper way, they need to be customized. We need to replace all calls to the EntityManager with DataManager’s methods invocations and add CUBA views support.

Some might argue that Spring approach is less manageable than CUBA’s because you do not have control over query generation process. This is always a problem of a balance between convenience and a level of abstraction and it is up to a developer to choose which way to go. But it looks like having an additional (not the only one), simpler way to deal with data won’t hurt.

And if you need more control in Spring, there is a way to specify your own query for an interface method, so this option should and will be added to CUBA, too.

Implementation

Query interfaces are implemented as a CUBA application module using spring-data-commons. This library contains classes for implementing custom query interfaces, for example, Spring’s spring-data-mongodb library is based on it. Spring-data-commons utilizes proxying technique to create proper implementations of the declared query interfaces.

During CUBA’s context initialization, all references to query interfaces are implicitly replaced by references to generated proxy beans published in the application context. When a developer invokes an interface method, it is intercepted by a corresponding proxy. Then, the proxy generates a JPQL query based on the method’s name, substitutes parameters values, and passes it to the DataManager for execution. The diagram below shows simplified interaction between key components of the module.

Image title


Using Query Interfaces in CUBA

To use CUBA query interfaces, you need to add an application module in the project’s build file:

appComponent("com.haulmont.addons.cuba.jpa.repositories:cuba-jpa-repositories-global:0.1-SNAPSHOT")


Here i the XML configuration to enable query interfaces:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:context="http://www.springframework.org/schema/context"
            xmlns:repositories="http://www.cuba-platform.org/schema/data/jpa"
            xsi:schemaLocation="
   http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-4.3.xsd
   http://www.cuba-platform.org/schema/data/jpa      
   http://www.cuba-platform.org/schema/data/jpa/cuba-repositories.xsd">

   <!-- Annotation-based beans -->
   <context:component-scan base-package="com.company.sample"/>

   <repositories:repositories base-package="com.company.sample.core.repositories"/>

</beans:beans>


If you prefer using annotations instead of creating XML configurations, you can enable query interfaces in the following way:

@Configuration
@EnableCubaRepositories
public class AppConfig {
   //Configuration here
}


After enabling query interfaces, you can create them in your application. An example of such an interface:

public interface CustomerRepository extends CubaJpaRepository<Customer, UUID> {

  long countByLastName(String lastName);

  List<Customer> findByNameIsIn(List<String> names);

  @CubaView("_minimal")
  @JpqlQuery("select c from sample$Customer c where c.name like concat(:name, '%')")
  List<Customer> findByNameStartingWith(String name);
}


This is the view for the entities that will be fetched (“_local” is the default view if not specified). The second annotation specifies the exact JPQL query that will be used for this method if the query cannot be expressed with a method name.

Query interfaces application component is attached to “global” CUBA module, so you can define and use query interfaces in both “core” and “web” modules, just don’t forget to enable them in the corresponding configuration files. The interface usage example is below:

@Service(CustomerService.NAME)
public class CustomerServiceBean implements PersonService {

   @Inject
   private CustomerRepository customerRepository;

   @Override
   public List<Date> getCustomersBirthDatesByLastName(String name) {
      return customerRepository.findByNameStartingWith(name)
            .stream().map(Customer::getBirthDate).collect(Collectors.toList());
   }
}


Conclusion

CUBA is flexible. If you feel that you need an additional feature for all your application and you don’t want to wait for the new version of CUBA, it is pretty easy to implement and add it not touching CUBA core. By adding query interfaces to CUBA, we hope to help developers work more efficiently, delivering reliable code faster. The first version of the library is available at GitHub and supports CUBA version 6.10 and higher.

Secure your Java app or API service quickly and easily with Okta's user authentication and authorization libraries. Developer accounts are free forever. Try Okta Instead.

Topics:
java ,cuba platform ,spring jpa ,Cuba ,Spring ,query interface ,query ,data manipulation ,JPQL

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}