Domain Entities and Services
This section describes entities using JDO annotations, but (the more widely used) JPA (in conjunction with Spring Data) is also supported. See the JDO/DataNucleus and JPA/Eclipselink documentation for more details on how to configure one or the other. |
Domain Entities
Domain entities are persistent domain objects, with their persistence handled an ORM (such as JDO/DataNucleus). As such, they are mapped to a persistent object store, typically an RDBMS, with the ORM taking care of both lazy loading and also the persisting of modified ("dirty") objects.
The vast majority of such entities will be annotated with @DomainObject(nature=ENTITY)
.
In addition they will also require ORM metadata.
-
for JPA/Eclipselink, this requires the
javax.persistence.Entity
annotation -
for JDO/DataNucleus, this requires the
@javax.jdo.annotations.PersistenceCapable
annotation.
It is also possible to specify ORM metadata using .xml files.
|
In this section we discuss the mechanics of writing domain objects that comply with Apache Causeway' programming model.
Entities (JPA)
JPA entities are typically defined using the @javax.persistence.Entity
annotation, with additional annotations to define their primary key, scalar properties and relationships to other entities.
For some examples, see:
See the JPA/Eclipselink object store for further information on annotating domain entities.
Entities (JDO)
See the JDO/DataNucleus object store for further information on annotating domain entities.
PersistenceCapable
This section is specific to the JDO API |
With the JDO API, entities are flagged as being "persistence capable", indicating how the ORM should manage their identity:
@javax.jdo.annotations.PersistenceCapable( (1)
identityType=IdentityType.DATASTORE, (2)
schema = "simple", (3)
table = "SimpleObject"
)
@javax.jdo.annotations.DatastoreIdentity( (4)
strategy=javax.jdo.annotations.IdGeneratorStrategy.IDENTITY,
column="id"
)
@javax.jdo.annotations.Version( (5)
strategy= VersionStrategy.DATE_TIME,
column="version"
)
@DomainObject( (6)
logicalTypeName = "simple.SimpleObject"
)
public class SimpleObject { /* ... */ }
1 | The @PersistenceCapable annotation indicates that this is an entity to DataNucleus.
The DataNucleus enhancer acts on the bytecode of compiled entities, injecting lazy loading and dirty object tracking functionality.
Enhanced entities end up also implementing the javax.jdo.spi.PersistenceCapable interface. |
2 | Indicates how identifiers for the entity are handled.
Using DATASTORE means that a DataNucleus is responsible for assigning the value (rather than the application). |
3 | Specifies the RDBMS database schema and table name for this entity will reside. The schema should correspond with the module in which the entity resides. The table will default to the entity name if omitted. |
4 | For entities that are using DATASTORE identity, indicates how the id will be assigned.
A common strategy is to allow the database to assign the id, for example using an identity column or a sequence. |
5 | The @Version annotation is useful for optimistic locking; the strategy indicates what to store in the version column. |
6 | The @DomainObject annotation identifies the domain object to Apache Causeway (not DataNucleus).
It isn’t necessary to include this annotation — at least, not for entities — but it is nevertheless recommended.
In particular, its strongly recommended that the logicalTypeName (which acts like an alias to the concrete domain class) is specified; note that it corresponds to the schema/table for DataNucleus' @PersistenceCapable annotation. |
Key Properties (JDO)
This section is specific to the JDO API |
All domain entities will have some sort of mandatory key properties.
The example below is a very simple case, where the entity is identified by a name
property.
This is often used in database unique indices, and in the toString()
implementation:
import javax.jdo.annotations.Column;
import javax.jdo.annotations.Unique;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.ToString.Include;
...
@Unique(
name="SimpleObject_name_UNQ", members = {"name"}) (1)
@ToString(onlyExplicitlyIncluded = true)
public class SimpleObject
implements Comparable<SimpleObject> { (2)
public SimpleObject(String name) {
setName(name);
}
@Column(allowsNull="false", length=50) (3)
@Getter @Setter (4)
@ToString.Include (5)
private String name;
private final static Comparator<SimpleObject> comparator =
Comparator.comparing(SimpleObject::getName);
@Override
public int compareTo(final SimpleObject other) {
return comparator.compare(this, other); (6)
}
}
1 | DataNucleus will automatically add a unique index to the primary surrogate id (discussed above), but additional alternative keys can be defined using the @Unique annotation.
In the example above, the "name" property is assumed to be unique. |
2 | Although not required, we strongly recommend that all entities are naturally Comparable .
This then allows parent/child relationships to be defined using SortedSet s; RDBMS after all are set-oriented. |
3 | Chances are that some of the properties of the entity will be mandatory, for example any properties that represent an alternate unique key to the entity.
The @Column annotation specifies the length of the column in the RDBMS, and whether it is mandatory.
Given there is a unique index on We can also represent this using a constructor that defines these mandatory properties. The ORM will create a no-arg constructor to allow domain entity to be rehydrated from the database at runtime (it then sets all state reflectively). |
4 | Use Lombok to generate the getters and setters for the name property itself. |
5 | Use Lombok to create a toString() implementation that includes the value of name property. |
6 | Use java.util.Comparator#comparing() to implement Comparable interface. |
Queries (JDO)
This section is specific to the JDO API |
When using JDO, it’s also common for domain entities to have queries annotated on them. These are used by repository domain services to query for instances of the entity:
...
@javax.jdo.annotations.Queries({
@javax.jdo.annotations.Query( (1)
name = "findByName", (2)
value = "SELECT " (3)
+ "FROM domainapp.modules.simple.dom.impl.SimpleObject " (4)
+ "WHERE name.indexOf(:name) >= 0 ") (5)
})
...
public class SimpleObject { /* ... */ }
1 | There may be several @Query annotations, nested within a @Queries annotation) defines queries using JDOQL. |
2 | Defines the name of the query. |
3 | The definition of the query, using JDOQL syntax. |
4 | The fully-qualified class name. Must correspond to the class on which the annotation is defined (the framework checks this automatically on bootstrapping). |
5 | In this particular query, is an implementation of a LIKE "name%" query. |
JDO/DataNucleus provides several APIs for defining queries, including entirely programmatic and type-safe APIs; but JDOQL is very similar to SQL and so easily learnt.
To actually use the above definition, the framework provides the RepositoryService. This is a generic repository for any domain class.
The corresponding repository method for the above query is:
public List<SimpleObject> findByName(String name) {
return repositoryService.allMatches( (1)
Query.named(SimpleObject.class, (2)
"findByName") (3)
.withParameter("name", name) (4)
);
}
@javax.inject.Inject
RepositoryService repositoryService;
1 | find all instances that match the query |
2 | Specifies the class that is annotated with @Query |
3 | Corresponds to the @Query#name attribute |
4 | Corresponds to the :name parameter in the query JDOQL string |
Domain Services
Domain services are (usually) singleton stateless services that provide additional functionality. This may be exposed in the UI, or might be invoked programmatically.
However, a service cannot have (persisted) properties, nor can it have (persisted) collections.
Domain services that are visible in the UI or REST API are annotated with @DomainService(), while services that are programmatic in nature should be simply annotated using Spring’s @Component or one of its specializations, eg @Service or @Repository.
Apache Causeway runs on top of Spring Boot, and relies on Spring Boot for dependency injection using @javax.inject.Inject
.
The @javax.annotation.Priority
annotation is used to prioritize multiple service implementations, effectively allowing any framework-provided domain service to be substituted out by a user-defined one encountered with an earlier precedence (= higher priority) if required.
This section looks at the programming model for writing your own domain services.
Nature of Service
Apache Causeway uses Spring Boot to instantiate and manage the dependency injection of domain services. The vast majority of these are singleton (application) scoped; a smaller number are request scoped (using the @CausewaySessionScope annotation).
Accordingly, all domain services are annotated or meta-annotated using Spring’s @Component annotation.
For domain services to be visible in the Apache Causeway UI, they must be annotated with @DomainService. Its #nature() attribute is either:
-
VIEW
(the default if not specified)which indicates that the actions should appear on the menu of the Web UI (Wicket viewer), and as top-level actions for the REST API provided by the REST API (Restful Objects viewer).
-
REST
which indicates that the actions should appear in the REST API provided by the REST API (Restful Objects viewer), but not rendered by the Web UI (Wicket viewer).
It’s also possible to define a "programmatic" domain service, meaning one that is instantiated and injected by Spring Boot, but is not visible in the UI or REST API. Such programmatic services are usually annotated with Spring’s @Service annotation or @Repository.
Repository and Factory
The repository/factory uses an injected RepositoryService to both instantiate new objects and to query the database for existing objects of a given entity type. Generally these services are not visible in UI, and so are annotated with @Repository
For example:
import org.springframework.stereotype.Repository;
import lombok.RequiredArgsConstructor;
@Repository (1)
@RequiredArgsConstructor(onConstructor_ = {@Inject} ) (2)
public CustomerRepository {
private final RepositoryService repositoryService;
public List<Customer> findByName(String name) {
return repositoryService.allMatches( (3)
Query.named(Customer.class, "findByName")
.withParameter("name", name));
}
public Customer newCustomer(...) {
Customer Customer =
repositoryService.detachedEntity(Customer.class); (4)
...
return repositoryService.persistAndFlush(Customer); (5)
}
public List<Customer> allCustomers() { (6)
return repositoryService.allInstances(Customer.class);
}
}
1 | Detected and managed by Spring Boot. |
2 | Lombok annotation for dependency injection of RepositoryService through generated constructor. |
3 | uses injected RepositoryService to query via JDOQL. |
4 | uses injected RepositoryService to instantiate a not-yet-persisted domain entity … |
5 | ... and then save into the database a new Customer instance. |
6 | Returns all instances (useful for prototyping, probably not for production). |
There is no need to annotate the actions; they are implicitly hidden because of the domain service’s nature.
JDO/Datanucleus also supports type-safe queries. These can be executed through the JdoSupportService domain service. |
Menu
Menu services provide actions to be rendered on the menu.
For the Web UI (Wicket viewer), each service’s actions appear as a collection of menu items of a named menu, and this menu is on one of the three menu bars provided by the Wicket viewer.
Although these can be organised using annotations, it’s usually easier to use a file-based layout file (menubars.layout.xml
).
For the REST API (Restful Objects viewer), all menu services are shown in the services representation.
import lombok.RequiredArgsConstructor;
@DomainService(nature = NatureOfService.VIEW) (1)
@RequiredArgsConstructor(onConstructor_ = {@Inject} ) (2)
public class Customers {
@Inject
protected final CustomerRepository customerRepository; (3)
@Action(semantics = SemanticsOf.SAFE)
public List<Customer> findByName( (4)
@ParameterLayout(named="Name") (5)
final String name ) {
return customerRepository.findByName(name); (6)
}
@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
public Customer newCustomer(...) {
return customerRepository.newCustomer(...);
}
@Action( semantics = SemanticsOf.SAFE,
restrictTo = RestrictTo.PROTOTYPING ) (7)
public List<Customer> listAll() {
return customerRepository.listAll();
}
}
1 | Identify the class as a domain service, to render in the menu. | ||
2 | Rendered in the UI as a "Find By Name" menu item underneath the "Customers" menu. | ||
3 | The @ParameterLayout provides metadata for the parameter itself, in this case its name.
|
||
4 | the action implementation delegates to an injected repository. The framework can inject into not just other domain services but will also automatically into domain entities and view models. There is further discussion of service injection below. | ||
5 | Prototype actions are rendered only in prototyping mode. A "list all" action such as this can be useful when exploring the domain with a small dataset. | ||
6 | Menu services typically delegate to an underlying repository/ies specific to the domain (rather than use RepositoryService directly, for example). |
Whether you separate out menu services from repository services is to some extent a matter of style. One perspective is that these two closely related domain services nevertheless have different responsibilities, and so could be kept separate. An alternative perspective is that the duplication is just unnecessary boilerplate, and conflicts with the naked objects philosophy.
Event Subscribers
Domain services acting as event subscribers can subscribe to lifecycle events, influencing the rendering and behaviour of other objects. Behind the scenes this uses Spring’s (in-memory) event bus.
import org.springframework.stereotype.Service;
import org.springframework.context.event.EventListener;
import lombok.RequiredArgsConstructor;
@Service (1)
@lombok.RequiredArgsConstructor(onConstructor_ = {@Inject} )
public class OnCustomerDeletedCascadeDeleteOrders {
private final OrderRepository orderRepository;
@EventListener(Customer.DeletedEvent.class) (2)
public void on(final Customer.DeletedEvent ev) { (3)
Customer customer = ev.getSource();
orderRepository.delete(customer);
}
}
1 | subscriptions do not appear in the UI at all |
2 | use Spring Framework’s @EventListener |
3 | the parameter type of the method corresponds to the event emitted on the event bus.
The actual method name does not matter (though it must have public visibility). |
Scoped services
By default all domain services are application-scoped, in other words singletons. Such domain services are required to be thread-safe, usually satisfied by being intrinsically stateless.
Sometimes though a service’s lifetime is applicable only to a single (http) request. The framework has a number of such services, including a Scratchpad service (to share adhoc data between methods), and QueryResultsCache, which as its name suggests will cache query results. Such services do hold state, but that state is scoped per (possibly concurrent) request and should be removed afterwards.
The requirement for request-scoped services is supported using Apache Causeway' own @CausewaySessionScope annotation (named because a short-lived CausewaySession
is created for each request).
This is used by the framework services and can also be used for user-defined services.
For example:
@Service
@CausewaySessionScope
public class MyService {
...
public void doSomething() { ... }
}
Unlike application-scoped services, these request-scoped services must be injected using a slightly different idiom (borrowed from CDI), using a javax.inject.Provider
.
For example:
import javax.inject.Provider;
public class SomeClient {
...
@Inject
Provider<MyService> myServiceProvider; (1)
public void someMethod() {
myServiceProvider.get() (2)
.doSomething();
}
1 | Inject using Provider |
2 | Obtain an instance using Provider#get() |
Configuration
Spring provides numerous mechanisms to configure domain services, both in terms of binding or passing in the configuration property to the service, and in terms of setting the value within some sort of configuration file.
The mechanism prefered by Apache Causeway itself, and which you are free to use for your own services, is the type-safe ConfigurationProperties, whereby the configuration properties are expressed in a series of nested static classes.
The simpleapp starter app includes an example:
import org.springframework.validation.annotation.Validated;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("app.simple-module")
@lombok.Data
@Validated
public static class Configuration {
private final Types types = new Types();
@lombok.Data
public static class Types {
private final Name name = new Name();
@lombok.Data
public static class Name {
private final Validation validation = new Validation();
@lombok.Data
public static class Validation {
private char[] prohibitedCharacters =
"!&%$".toCharArray();
private String message =
"Character '{character}' is not allowed";
}
}
}
}
This configuration property can be injected, like any other component, and makes the configuration value available in a type-safe fashion:
val prohibitedCharacters =
config.getTypes().getName().getValidation().getProhibitedCharacters();
For this configuration property service to be discovered and managed by Spring, we need to use the EnableConfigurationProperties annotation. This normally would reside on the owning module (discussed in more detail later):
import org.springframework.context.annotation.Configuration
@Configuration
// ...
@EnableConfigurationProperties({
SimpleModule.Configuration.class,
})
public class SimpleModule /* ... */ {
// ...
}
These configuration properties can then be specified using either Spring’s application.yml
or application.properties
.
For example:
app:
simple-module:
types:
name:
validation:
message: "'{character}' is invalid."
prohibited-characters: "&%$"
Moreover, Spring is able to configure the IDE so that these configuration values can be specified using code completion. All that is required is this dependency:
<!-- IDE support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
Initialization
Sometimes a domain service needs to perform initialization logic before it is ready to be used.
In many cases, such initialization can be performed within the constructor. If the initialization has dependencies, then these can be injected using standard constructor injection.
Alternatively, initialization can be moved into a @PostConstruct
lifecycle callback.
Shutdown is similar; the framework will call any method annotated with javax.annotation.PreDestroy
.
Injecting services
Apache Causeway runs on top of Spring Boot, and uses Spring Boot for dependency injection, both the application’s own domain services and also the many additional services defined by the framework (such as RepositoryService).
Since this is a core capability of Spring, it’s worth checking out Spring’s documentation on the topic.
Injection is requested using the JEE @javax.inject.Inject annotation. This is described in Spring’s documentation, using JSR330 standard annotations. It is also possible to use Spring’s own @Autowired annotation. Since the two annotations are effectively equivalent, we recommend using the JEE standard. |
However, not only does Apache Causeway use Spring to autowire domain services into other services, the framework also ensures that services are injected into any domain object (eg entity, view model, mixins, fixture script, specification etc). This is key enabler to place functionality in the "right place", eg in a domain entity/view model itself, or in a mixin.
There are three ways in which to inject the domain services:
Whether you use setter or field injection for domain objects etc is a matter of style. Generally field injection is somewhat frowned up.
Constructor Injection.
As noted above, constructor injection is only available for domain services. For example:
import org.springframework.data.repository.Repository; (1)
@Repository
public class CustomerRepository {
private final RepositoryService repositoryService;
public CustomerRepository(
final RepositoryService repositoryService) {
this.repositoryService = repositoryService;
}
// ...
}
1 | indicates this is a repository service. |
If you wish, Project Lombok can be used to remove some of the boilerplate:
import org.springframework.stereotype.Repository;
import lombok.RequiredArgsConstructor;
@Repository
@RequiredArgsConstructor(onConstructor_ = {@Inject} ) (1)
public class CustomerRepository {
private final RepositoryService repositoryService;
// ...
}
1 | Generates a constructor for all final fields. |
If the layering between services is well defined, as in the above example (application CustomerRepository
depends upon framework RepositoryService
), then constructor injection should work out.
However, Spring does not support
TODO: Cyclic dependencies
Dependency Resolution Process, ("Circular dependencies" sidebar).
TODO: Provider<>
Setter and Field Injection
Setter or field injection must be used all objects other than domain services. For example, setter injection is:
import javax.inject.Inject;
public class Customer {
...
OrderRepository orderRepository;
@Inject (1)
public void setOrderRepository(orderRepository) {
this.orderRepository = orderRepository;
}
}
1 | The framework injects the domain service into the entity, before any further interactions with it. |
It’s not necessary for the visibility to be public
, so it should be as restrictive as possible.
In many cases, default visibility will work (assuming unit tests that mock the dependency are in the same package).
Some of the boilerplate can be removed using Project Lombok:
import javax.inject.Inject;
import lombok.Setter;
public class Customer {
...
@Setter(value= AccessLevel.PACKAGE, onMethod_ = {Inject.class}) (1)
OrderRepository orderRepository;
}
1 | Generates a package-level setter, annotated with @Inject |
If you want to use field injection, then this is simply:
import javax.inject.Inject;
public class Customer {
...
@Inject OrderRepository orderRepository;
}
... and live with or disable any IDE warnings relating to field injection.
Using default visibility here still allows the field to be mocked out within unit tests (if placed in the same package as the code under test).
Multiple Implementations
If there is more than one implementation of the service, then a specific implementation can be requested using either Spring’s @Primary annotation (further discussion here) or Spring’s Qualifier annotation (further discussion here).
All of the domain services provided by Apache Causeway' are annotated with @Qualifier
to enable this.
Injecting Lists of Services
It’s also possible to inject a list of services:
import javax.inject.Inject;
public class DocumentService {
...
@Inject
List<PaperclipFactory> paperclipFactories;
}
These will be in the order as defined by the @javax.annotation.Priority
annotation.
This pattern can be useful when implementing the chain of responsibility design pattern, that is, looking for the first implementation that can handle a request.
It is also useful to "broadcast" or fan out an implementation. For example, the framework defines the ExecutionSubscriber SPI, which is used to publish Interaction Executions to external systems. The framework provides a simple logging implementation, which will always be called. All other implementations available will also be called.
Injecting `CausewaySessionScope`d services
Most domain services are application-scoped, in other words they are stateless global singletons that are shared by all concurrent requests.
A small number of framework-provided services are annotated using @CausewaySessionScope. This means that they are stateful and scoped with each causeway session, in other words HTTP request. One such service is QueryResultsCache, used for performance caching.
These domain services must be requested using a slightly different idiom, using the Provider
interface.
For example:
import javax.inject.Inject;
import javax.inject.Provider;
public class Customer {
...
@Inject OrderRepository orderRepository;
@Inject Provider<QueryResultsCache> queryResultsCacheProvider; (1)
public List<Order> getOrders() {
Customer customer = this;
return queryResultsCacheProvider
.get() (2)
.execute(
() -> orderRepository.findByCustomer(customer),
Customer.class, "getOrders",
customer)
);
}
1 | inject a Provider for the service, not directly |
2 | Get the cache from the provider |
If you accidentally inject the service directly (without being wrapped in Provider
), then the framework will detect this and fail-fast.
Object Management (CRUD)
This chapter shows the idioms for creating, reading, updating and deleting domain entities. The main domain services used for this are RepositoryService and FactoryService.
Instantiating
Domain entities can be instantiated using the FactoryService provided by the framework. For example:
Customer customer = factoryService.detachedEntity(Customer.class);
The returned domain entity is not persistent and is unknown to the ORM; hence "detached".
When the framework instantiates the object, all services are injected into the framework, and an ObjectCreatedEvent
lifecycle event will also be emitted.
You may prefer however for your domain entities to have regular constructor defining their minimum set of mandatory properties. For example:
public class Customer {
public Customer(String reference, String firstName, String lastName) {
// ...
}
// ...
}
In such cases, the domain object cannot be instantiated using FactoryService. Instead the ServiceInjector service can be used to inject services:
Customer customer = new Customer(reference, firstName, lastName);
factoryService.detachedEntity(customer);
If you prefer, this can be performed in one step:
Customer customer = factoryService.detachedEntity(
new Customer(reference, firstName, lastName));
Note though that this does not raise any lifecycle event.
Persisting
Once a domain entity has been instantiated and initialized, it can be persisted using the RepositoryService.
For example:
Customer customer = ...
repositoryService.persist(customer);
If using the no-arg form to instantiate the entity, then (to save having to inject the FactoryService
as well), the RepositoryService
can also be used to instantiate.
This gives rise to this common idiom:
Customer customer = repositoryService.instantiate(Customer.class);
customer.setReference(reference);
customer.setFirstName(firstName);
customer.setLastName(lastName);
...
repositoryService.persist(customer);
On the other hand, there is often little need to inject services into the domain entity between its instantiation and persistence. If the domain entity has an N-arg constructor, then the code is often simpler:
Customer customer = repositoryService.persist(new Customer(reference, name, lastname));
Note that the persist()
returns the object passed to it.
Eager Persistence
It’s worth being aware that the framework does not eagerly persist the object.
Rather, it queues up an internal command structure representing the object persistence request.
This is then executed either at the end of the transaction, or if a query is run, or if the internal queue is manually flushed using TransactionService's flush()
method.
Flushing also happens when a repository query is executed, so that the pending persist operation is performed first.
Generally therefore the lazy persistence approach works well enough.
Nevertheless, if you want to ensure that the persist command is flushed immediately, you can use:
repositoryService.persistAndFlush(customer);
When an object is persisted the framework will emit ObjectPersistingEvent
and ObjectPersistedEvent
lifecycle events.
Persistence by Reachability (JDO)
If using JDO/DataNucleus, it is possible to configure ORM to automatically persist domain entities if they are associated with other already-persistent entities. This avoid the need to explicitly call "persist".
This is done using persistence-by-reachability configuration property:
datanucleus.persistence-by-reachability-at-commit=true
One downside is that the code is arguably less easy to debug, and there may be performance implications.
Finding Objects
Retrieving domain entities depends on the ORM, though the RepositoryService can be used as an abstraction over either if required.
Finding Objects (JPA)
The easiest way to retrieve domain entities if using JPA is to leverage the capabilities of Spring Data.
For example, simply by declaring this interface:
public interface UserRepository extends Repository<User, Long> {
List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}
and Spring Data will create an implementation based on naming conventions. See the Spring Data documentation for further details.
It is also possible to declare JPQL queries , either on the repository method (using javax.persistence.Query
) or on the entity (using javax.persistence.NamedQuery
).
On the entity, declare a named query, eg:
@javax.persistence.Entity
@javax.persistence.NamedQueries({
@javax.persistence.NamedQuery( (1)
name = "Customer.findByNameLike", (2)
query = "SELECT c " + (3)
"FROM Customer c " + (4)
"WHERE c.name LIKE :name" (5)
)
})
...
public class Customer {
// ...
}
1 | There may be several @NamedQuery annotations, nested within a @NamedQueries annotation, defining queries using JPQL. |
2 | Defines the name of the query. |
3 | The definition of the query, using JPQL syntax. |
4 | The table name |
5 | The predicate, expressed using SQL syntax. |
and in the corresponding repository, use RepositoryService:
import org.springframework.stereotype.Repository;
import lombok.RequiredArgsConstructor;
@Repository
@RequiredArgsConstructor(onConstructor_ = {@Inject} )
public class CustomerRepository {
private final RepositoryService repositoryService;
public List<Customer> findByName(String name) {
return repositoryService.allMatches( (1)
Query.named(Customer.class, "Customer.findByNameLike") (2)
.withParameter("name", "%" + name + "%"); (3)
}
}
1 | The RepositoryService is a generic facade over the ORM API. |
2 | Specifies the class that is annotated with @NamedQuery, along with the @NamedQuery#name attribute |
3 | The :name parameter in the query JPQL string, and its corresponding value |
Finding Objects (JDO)
In the case of JDO/DataNucleus, it typically requires a JDOQL query defined on the domain entity, and a corresponding repository service for that domain entity type. This repository calls the framework-provided RepositoryService to actually submit the query.
For example:
@javax.jdo.annotations.PersistenceCapable
@javax.jdo.annotations.Queries({
@javax.jdo.annotations.Query( (1)
name = "findByName", (2)
value = "SELECT " (3)
+ "FROM com.mydomain.myapp.Customer " (4)
+ "WHERE name.indexOf(:name) >= 0 ") (5)
})
...
public class Customer {
// ...
}
1 | There may be several @Query annotations, nested within a @Queries annotation, defining queries using JDOQL. |
2 | Defines the name of the query. |
3 | The definition of the query, using JDOQL syntax. |
4 | The fully-qualified class name. Must correspond to the class on which the annotation is defined (the framework checks this automatically on bootstrapping). |
5 | The predicate, expressed using Java syntax. In this particular query, is an implementation of a LIKE "name%" query. |
and in the corresponding repository, use RepositoryService:
import org.springframework.stereotype.Repository;
import lombok.RequiredArgsConstructor;
@Repository
@RequiredArgsConstructor(onConstructor_ = {@Inject} )
public class CustomerRepository {
private final RepositoryService repositoryService;
public List<Customer> findByName(String name) {
return repositoryService.allMatches( (1)
Query.named(Customer.class, "findByName") (2)
.withParameter("name", name); (3)
}
}
1 | The RepositoryService is a generic facade over the ORM API. |
2 | Specifies the class that is annotated with @Query, along with the @Query#name attribute |
3 | The :name parameter in the query JDOQL string, and its corresponding value |
Whenever a query is submitted, the framework will automatically "flush" any pending changes. This ensures that the database query runs against an up-to-date table so that all matching instances (with respect to the current transaction) are correctly retrieved.
When an object is loaded from the database the framework will emit ObjectLoadedEvent
lifecycle event.
Type-safe queries
DataNucleus also supports type-safe queries; these can be executed using the JdoSupportService (JDO-specific) domain service.
See JdoSupportService for further details.
Updating Objects
There is no specific API to update a domain entity. Rather, the ORM (DataNucleus) automatically keeps track of the state of each object and will update the corresponding database rows when the transaction completes.
That said, it is possible to "flush" pending changes:
-
TransactionService acts at the Apache Causeway layer, and flushes any pending object persistence or object deletions
-
(if using JDO/DataNucleus), the JdoSupportService domain service can be used reach down to the underlying JDO API, and perform a flush of pending object updates also.
When an object is updated the framework will emit ObjectUpdatingEvent
and ObjectUpdatedEvent
lifecycle events.
Deleting Objects
Domain entities can be deleted using RepositoryService. For example:
Customer customer = ...
repositoryService.remove(customer);
It’s worth being aware that (as for persisting new entities) the framework does not eagerly delete the object.
Rather, it queues up an internal command structure representing the object deletion request.
This is then executed either at the end of the transaction, or if a query is run, or if the internal queue is manually flushed using TransactionService's flush()
method.
Alternatively, you can use:
repositoryService.removeAndFlush(customer);
to eagerly perform the object deletion from the database.
When an object is deleted the framework will emit ObjectRemovingEvent
lifecycle event.