No technology exists in a vacuum, and Jakarta Persistence is no different in this regard. Although the fat-client style of application demonstrated in the previous chapter is a viable use of Jakarta Persistence, the majority of Enterprise Java applications are deployed to an application server, typically using Jakarta EE web technologies, and possibly other technologies as well. Therefore, it is essential to understand the components that make up a deployed application and the role of Jakarta Persistence in this environment.
This book deals with Jakarta Persistence in Jakarta EE 10, which main new features includes further alignment with CDI, introduction of CDI-Lite and the new Core Profile, which contains a set of Jakarta EE Specifications targeting smaller runtimes suitable for microservices and ahead-of-time compilation.
We begin with an overview of the major Jakarta EE technologies relevant to persistence. As part of this overview, we describe the Jakarta Enterprise Beans component model, demonstrating the basic syntax for some of the different types of Enterprise Beans.
We then go on to briefly cover the standard dependency injection (DI) mechanism, mostly using the Jakarta EE Contexts and Dependency Injection (CDI) approach. This chapter is not intended to be a complete or detailed exploration of Jakarta EE or component frameworks, and we can’t possibly go into all of the DI frameworks in the DI sphere, or even the facilities offered by CDI. However, the CDI and Enterprise Beans examples are fairly typical of DI in general and should give readers a general idea of how Jakarta Persistence can be used with DI-enabled components, be they of the Jakarta EE variety or some other DI container component, such as Spring or Guice.
We then look at transactions, another application server technology that has had a major impact on applications using Jakarta Persistence. Transactions are a fundamental element of any enterprise application that needs to ensure data integrity.
Finally, we explain how to use the technologies described in this chapter within the context of how persistence integrates into each component technology. We also revisit the Java SE application from the previous chapter and retarget it to the enterprise platform.
Application Component Models
The word “component” has taken on many meanings in software development, so let’s begin with a definition. A component is a self-contained, reusable software unit that can be integrated into an application. Clients interact with components via a well-defined contract. In Java, the simplest form of software component is the JavaBean, commonly referred to as just a bean. Beans are components implemented in terms of a single class whose contract is defined by the naming patterns of the methods on the bean. The JavaBean naming patterns are so common now that it is easy to forget that they were originally intended to give user-interface builders a standard way of dealing with third-party components.
In the enterprise space, components focus more on implementing business services, with the contract of the component defined in terms of the business operations that can be carried out by that component. The traditional component model for Jakarta EE has always been the Enterprise Beans model, which defines ways to package, deploy, and interact with self-contained business services. Then, CDI came along and brought a more powerful and flexible model of the managed bean component, with CDI beans being either Enterprise Beans or non-Enterprise Beans Java classes.
Choosing whether to use a component model in your application is largely personal preference, but is generally a good design choice. Using components requires organizing the application into layers, with business services living in the component model and presentation services layered on top of it.
Loose coupling : Using components to implement services encourages loose coupling between layers of an application. The implementation of a component can change without any impact to the clients or other components that depend on it.
Dependency management : Dependencies for a component can be declared in metadata and automatically resolved by the container.
Lifecycle management : The lifecycle of components is well defined and managed by the application server. Component implementations can participate in lifecycle operations to acquire and release resources, or perform other initialization and shutdown behavior.
Declarative container services : Business methods for components are intercepted by the application server in order to apply services such as concurrency, transaction management, security, and remoting.
Portability : Components that comply to Jakarta EE standards and that are deployed to standards-based servers can be more easily ported from one compliant server to another.
Scalability and reliability: Application servers are designed to ensure that components are managed efficiently with an eye to scalability. Depending on the component type and server configuration, business operations implemented using components can retry failed method calls or even fail over to another server in a cluster.
As you read this book, you will notice that in some cases, an example will use a component to house the business logic and invoke the Jakarta Persistence API. In many cases it will be a session bean, in other cases it will be a non-Enterprise Beans CDI bean, and in still others it will be a CDI-managed session bean (session bean with a scope). Session beans are the preferred component because they are the simplest to write and configure and a natural fit for interacting with Jakarta Persistence. The actual component type we use is less important (the type of component is largely substitutable as long as it is managed by a container that supports Jakarta Persistence and transactions) than illustrating how components fit into the Jakarta Persistence and can invoke it.
Session Beans
Session beans are a component technology designed to encapsulate business services. The client accessible operations supported by the service may be defined using a Java interface or, in the absence of an interface, just the set of public methods on the bean implementation class. The bean class is little more than a regular Java class, and yet, by virtue of being part of the Enterprise Beans component model, the bean has access to a wide array of container services. The significance of the name “session bean” has to do with the way in which clients access and interact with them. Once a client acquires a reference to a session bean from the server, it starts a session with that bean and can invoke business operations on it.
There are three types of session beans: stateless, stateful, and singleton. Interaction with a stateless session bean begins at the start of a business method call and ends when the method call completes. There is no state that carries over from one business operation to the other. An interaction with stateful session beans becomes more of a conversation that begins from the moment the client acquires a reference to the session bean and ends when the client explicitly releases it back to the server. Business operations on a stateful session bean can maintain state on the bean instance across calls. We provide more detail on the implementation considerations of this difference in interaction style as we describe each type of session bean.
Singleton session beans can be considered a hybrid of stateless and stateful session beans. All clients share the same singleton bean instance, so it becomes possible to share state across method invocations, but singleton session beans lack the conversational contract and mobility of stateful session beans. State on a singleton session bean also raises issues of concurrency that need to be taken into consideration when deciding whether to use this style of session bean.
As with most component containers, clients in an Enterprise Beans container do not interact directly with a session bean instance. The client references and invokes an implementation of the business interface or bean class provided by the server. This implementation class acts as a proxy to the underlying bean implementation. This decoupling of client from bean allows the server to intercept method calls in order to provide the services required by the bean, such as transaction management. It also allows the server to optimize and reuse instances of the session bean class as necessary.
In the following sections, we discuss session beans using synchronous business method invocations. Asynchronous business methods offer an alternative invocation pattern involving futures, but are beyond the scope of this book.
Stateless Session Beans
As mentioned, a stateless session bean sets out to complete an operation within the lifetime of a single method. Stateless beans can implement many business operations, but each method cannot assume that any other was invoked before it.
This might sound like a limitation of the stateless bean, but it is by far the most common form of business service implementation. Unlike stateful session beans, which are good for accumulating state during a conversation (such as the shopping cart of a retail application), stateless session beans are designed to carry out independent operations very efficiently. Stateless session beans can scale to large numbers of clients with minimal impact to overall server resources.
Defining a Stateless Session Bean
Zero or more business interfaces that define what methods a client can invoke on the bean. When no interface is defined, the set of public methods on the bean implementation class forms a logical client interface.
A class that implements these interfaces, called the bean class, which is marked with the @Stateless annotation.
Whether you want to front your session bean with an actual interface or not is a matter of preference. We show examples of both, but generally don’t use the interface in subsequent examples.
The Business Interface for a Session Bean
The Bean Class Implementing the HelloService Interface
A Session Bean with No Interface
The logical interface of the session bean consists of its public methods, in this case, the sayHello() method. Clients use the HelloServiceBean class as if it were an interface and must disregard any nonpublic methods or details of the implementation. Under the covers, the client will be interacting with a proxy that extends the bean class and overrides the business methods to provide the standard container services.
Lifecycle Callbacks
Unlike a regular Java class used in application code, the server manages the lifecycle of a stateless session bean. The server decides when to create and remove bean instances and has to initialize the services for a bean instance after it is constructed, but before the business logic of the bean is invoked. Likewise, the bean might have to acquire a resource, such as a JDBC data source, before business methods can be used. However, in order for the bean to acquire a resource, the server must first have completed initializing its services for the bean. This limits the usefulness of the constructor for the class because the bean won’t have access to any resources until server initialization has completed.
To allow both the server and the bean to achieve their initialization requirements, Enterprise Beans support lifecycle callback methods that are invoked by the server at various points in the bean’s lifecycle. For stateless session beans, there are two lifecycle callbacks: PostConstruct and PreDestroy. The server will invoke the PostConstruct callback as soon as it has completed initializing all the container services for the bean. In effect, this replaces the constructor as the location for initialization logic because it is only here that container services are guaranteed to be available. The server invokes the PreDestroy callback immediately before the server releases the bean instance to be garbage-collected. Any resources acquired during PostConstruct that require explicit shutdown should be released during PreDestroy.
Using the PostConstruct Callback to Acquire a Logger
Stateful Session Beans
In the introduction to session beans, we described the difference between stateless and stateful beans as being based on the interaction style between client and server. In the case of stateless session beans, that interaction started and ended with a single method call. Sometimes clients need to issue multiple requests and have each request be able to access or consider the results of previous requests. Stateful session beans are designed to handle this scenario by providing a dedicated service to a client that starts when the client obtains a reference to the bean and ends only when the client chooses to end the conversation.
The quintessential example of the stateful session bean is the shopping cart of an ecommerce application. The client obtains a reference to the shopping cart, starting the conversation. Over the span of the user session, the client adds or removes items from the shopping cart, which maintains state specific to the client. Then, when the session is complete, the client completes the purchase, causing the shopping cart to be removed.
This is not unlike using a nonmanaged Java object in application code. You create an instance, invoke operations on the object that accumulate state, and then dispose of the object when you no longer need it. The only difference with the stateful session bean is that the server manages the actual object instance and the client interacts with that instance indirectly through a proxy object.
Stateful session beans offer a superset of the functionality available in stateless session beans. The features that we covered for stateless session beans apply equally to stateful session beans.
Defining a Stateful Session Bean
Implementing a Shopping Cart Using a Stateful Session Bean
There are two things different in this bean compared with the stateless session beans we have been dealing with so far.
The first difference is that the bean class has state fields that are modified by the business methods of the bean. This is allowed because the client that uses the bean effectively has access to a private instance of the session bean on which to make changes.
The second difference is that there are methods marked with the @Remove annotation. These are the methods that the client will use to end the conversation with the bean. After one of these methods has been called, the server will destroy the bean instance, and the client reference will throw an exception if any further attempt is made to invoke business methods. Every stateful session bean must define at least one method marked with the @Remove annotation, even if the method doesn’t do anything other than serve as an end to the conversation. In Listing 3-5, the checkout() method is called if the user completes the shopping transaction, although cancel() is called if the user decides not to proceed. The session bean is removed in either case .
Lifecycle Callbacks
Like the stateless session bean, the stateful session bean also supports lifecycle callbacks in order to facilitate bean initialization and cleanup. It also supports two additional callbacks to allow the bean to gracefully handle passivation and activation of the bean instance. Passivation is the process by which the server serializes the bean instance so that it can either be stored offline to free up resources or replicated to another server in a cluster. Activation is the process of deserializing a passivated session bean instance and making it active in the server once again. Because stateful session beans hold state on behalf of a client and are not removed until the client invokes one of the remove methods on the bean, the server cannot destroy a bean instance to free up resources. Passivation allows the server to temporarily reclaim resources while preserving session state.
Before a bean is passivated, the server will invoke the PrePassivate callback. The bean uses this callback to prepare the bean for serialization, usually by closing any live connections to other server resources. The PrePassivate method is identified by the @PrePassivate marker annotation. After a bean has been activated, the server will invoke the PostActivate callback. With the serialized instance restored, the bean must then reacquire any connections to other resources that the business methods of the bean might be depending on. The PostActivate method is identified by the @PostActivate marker annotation.
Using Lifecycle Callbacks on a Stateful Session Bean
Singleton Session Beans
Two of the most common criticisms of the stateless session bean have been the perceived overhead of bean pooling and the inability to share state via static fields. The singleton session bean attempts to provide a solution to both concerns by providing a single shared bean instance that can both be accessed concurrently and used as a mechanism for shared state. Singleton session beans share the same lifecycle callbacks as a stateless session bean, and server-managed resources such as persistence contexts behave the same as if they were part of a stateless session bean. But the similarities end there because singleton session beans have a different overall lifecycle than stateless session beans and have the added complexity of developer-controlled locking for synchronization.
Unlike other session beans, the singleton can be declared to be created eagerly during application initialization and exist until the application shuts down. Once created, it will continue to exist until the container removes it, regardless of any exceptions that occur during business method execution. This is a key difference from other session bean types because the bean instance will never be re-created in the event of a system exception. In case of stateful or stateless session beans, the exception causes the bean instance to be discarded by the container.
The long life and shared instance of the singleton session bean make it the ideal place to store common application state, whether read-only or read-write. To safeguard access to this state, the singleton session bean provides a number of concurrency options depending on the needs of the application developer. Methods can be completely unsynchronized for performance, or automatically locked and managed by the container.
Defining a Singleton Session Bean
Implementing a Singleton Session Bean
If you compare the HitCounter bean in Listing 3-7 with the stateless and stateful session beans defined earlier, you can see two immediate differences. Unlike the stateless session bean, there is state in the form of a count field used to capture the visit count. But unlike the stateful session bean, there is no @Remove annotation to identify the business method that will complete the session.
By default, the container will manage the synchronization of the business methods to ensure that data corruption does not occur. In this example, that means all access to the bean is serialized so that only one client is invoking a business method on the instance at any time.
The lifecycle of the singleton session bean is tied to the lifecycle of the overall application. The container determines the point when the singleton instance gets created unless the bean class is annotated with the @Startup annotation to force eager initialization when the application starts. The container can create singletons that do not specify eager initialization lazily, but this is vendor-specific and cannot be assumed.
Lifecycle Callbacks
The lifecycle callbacks for singleton session beans are the same as for stateless session beans: PostConstruct and PreDestroy. The container will invoke the PostConstruct callback after server initialization of the bean instance and likewise invoke the PreDestroy callback prior to disposing of the bean instance. The key difference here with respect to stateless session beans is that PreDestroy is invoked only when the application shuts down as a whole. It will therefore be called only once, whereas the lifecycle callbacks of stateless session beans are called frequently as bean instances are created and destroyed.
Servlets
Servlets are a component technology designed to serve the needs of web developers who need to respond to HTTP requests and generate dynamic content in return. Servlets are the oldest and most popular technology introduced as part of the Jakarta EE platform. They are the foundation for technologies such as Jakarta Server Pages and the backbone of web frameworks such as Jakarta Server Faces (Jakarta Faces).
Although you might have some experience with servlets, it is worth describing the impact that web application models have had on enterprise application development. Because of its reliance on the HTTP protocol, the Web is inherently a stateless medium. Much like the stateless session beans described earlier, a client makes a request, the server triggers the appropriate service method in the servlet, and content is generated and returned to the client. Each request is entirely independent from the last.
This presents a challenge because many web applications involve some kind of conversation between the client and the server in which the previous actions of the user influence the results returned on subsequent pages. To maintain that conversational state, many early applications attempted to dynamically embed context information into URLs. Unfortunately, not only does this technique not scale very well but it also requires a dynamic element to all content generation that makes it difficult for nondevelopers to write content for a web application.
Servlets solve the problem of conversational state with the session. Not to be confused with the session bean, the HTTP session is a map of data associated with a session ID. When the application requests that a session be created, the server generates a new ID and returns an HTTPSession object that the application can use to store key-value pairs of data. It then uses techniques such as browser cookies to link the session ID with the client, tying the two together into a conversation. For web applications, the client is largely ignorant of the conversational state that is tracked by the server.
Maintaining Conversational State with a Servlet
The rise of application frameworks targeted to the Web has also changed the way in which we develop web applications. Application code written in servlets is rapidly being replaced with application code further abstracted from the base model using frameworks such as Jakarta Faces. When working in an environment such as this, basic application persistence issues, such as where to acquire and store the entity manager and how to effectively use transactions quickly, become relevant.
Although we explore some of these issues, persistence in the context of a framework such as Jakarta Faces is beyond the scope of this book. As a general solution, we recommend adopting a component model in which to focus persistence operations. Session beans, for example, are easily accessible from anywhere within a Jakarta EE application, making them perfect neutral ground for business services. The ability to exchange entities inside and outside of the session bean model means that the results of persistence operations will be directly usable in web frameworks without having to tightly couple your presentation code to the persistence API.
Dependency Management and CDI
The business logic of a Jakarta EE component is typically not completely self-contained. More often than not, the implementation has dependencies on other resources. This might include server resources, such as a JDBC data source, or application-defined resources, such as another component or entity manager for a specific persistence unit. The core Jakarta EE platform contains some fairly limited support for injecting dependencies into a limited number of predefined server resources, such as data sources, managed transactions, and others. However, the CDI standard goes well beyond simple dependency injection (DI) and provides an extensive framework to support a full range of requirements, from the trivial to the exotic. We begin by describing the basic concepts and support contained within the platform from before CDI and then move on to CDI and its contextual DI model.
Jakarta EE components support the notion of references to resources. A reference is a named link to a resource that can be resolved dynamically at runtime from within application code or resolved automatically by the container when the component instance is created. We cover each of these scenarios shortly.
A reference consists of two parts: a name and a target. The name is used by application code to resolve the reference dynamically, whereas the server uses target information to find the resource the application is looking for. The type of resource to be located determines the type of information required to match the target. Each resource reference requires a different set of information specific to the resource type to which it refers.
A reference is declared using one of the resource reference annotations: @Resource, @EJB, @PersistenceContext, or @PersistenceUnit. These annotations can be placed on a class, field, or setter method. The choice of location determines the default name of the reference, and whether or not the server resolves the reference automatically.
Dependency Lookup
The first strategy for resolving dependencies in application code that we discuss is called the dependency lookup . This is the traditional form of dependency management in Jakarta EE, in which the application code is responsible for looking up a named reference using the Java Naming and Directory Interface (JNDI).
All the resource annotations support an attribute called name that defines the JNDI name of the reference. When the resource annotation is placed on the class definition, this attribute is mandatory. If the resource annotation is placed on a field or a setter method, the server will generate a default name. When using dependency lookup, annotations are typically placed at the class level, and the name is explicitly specified. Placing a resource reference on a field or setter method has other effects besides generating a default name that we discuss in the next section.
The role of the name is to provide a way for the client to resolve the reference dynamically. Every Jakarta EE application server supports JNDI, even though it is less frequently used by applications since the advent of dependency injection, and each Jakarta EE component has its own locally scoped JNDI naming context called the environment naming context. The name of the reference is bound into the environment naming context, and when it is looked up using the JNDI API, the server resolves the reference and returns the target of the reference.
Looking Up an Enterprise Bean Dependency
Using the EJBContext lookup() Method
The EJBContext lookup() method has two advantages over the JNDI API. The first is that the argument to the method is the name exactly as it was specified in the resource reference. The second is that only runtime exceptions are thrown from the lookup() method so the checked exception handling of the JNDI API can be avoided. Behind the scenes, the exact same sequence of JNDI API calls from Listing 3-9 is being made, but the JNDI exceptions are handled automatically.
Dependency Injection
When a resource annotation is placed on a field or setter method, two things occur. First, a resource reference is declared just as if it had been placed on the bean class (similar to the way the @EJB annotation worked in the example in Listing 3-9), and the name for that resource will be bound into the environment naming context when the component is created. Second, the server does the lookup automatically on your behalf and sets the result into the instantiated class.
The process of automatically looking up a resource and setting it into the class is called dependency injection because the server is said to inject the resolved dependency into the class. This technique, one of several commonly referred to as inversion of control, removes the burden of manually looking up resources from the JNDI environment context.
Dependency injection is considered a best practice for application development, not only because it reduces the need for JNDI lookups but also because it simplifies testing. Without any JNDI API code in the class that has dependencies on the application server runtime environment, the bean class can be instantiated directly in a unit test. The developer can then manually supply the required dependencies and test the functionality of the class in question instead of worrying about how to work around the JNDI lookup.
Field Injection
Using Field Injection
Field injection is certainly the easiest to implement, and the examples in this book always opt to use this form rather than the dynamic lookup form. The only thing to consider with field injection is that if you are planning on unit testing, you need either to add a setter method or make the field accessible to your unit tests to manually satisfy the dependency. Private fields, although legal, require unpleasant hacks if there is no accessible way to set their value. Consider package scope for field injection if you want to unit test without having to add a setter.
We mentioned in the previous section that a name is automatically generated for the reference when a resource annotation is placed on a field or setter method. For completeness, we describe the format of this name, but it is unlikely that you will find many opportunities to use it. The generated name is the fully qualified class name, followed by a forward slash and then the name of the field or property. This means that if the DeptService bean is located in the persistence.session package, the injected Enterprise Beans referenced in Listing 3-9 would be accessible in the environment naming context under the name persistence.session.DeptService/audit. Specifying the name element for the resource annotation will override this default value .
Setter Injection
Using Setter Injection
This style of injection allows for private fields, yet also works well with unit testing. Each test can simply instantiate the bean class and manually perform the dependency injection by invoking the setter method, usually by providing an implementation of the required resource that is tailored to the test .
Declaring Dependencies
The following sections describe some of the resource annotations described in the Jakarta EE specification. Each annotation has a name attribute for optionally specifying the reference name for the dependency. Other attributes on the annotations are specific to the type of resource that needs to be acquired.
Referencing a Persistence Context
In the previous chapter, we demonstrated how to create an entity manager for a persistence context using an EntityManagerFactory returned from the Persistence class. In the Jakarta EE environment, the @PersistenceContext annotation can be used to declare a dependency on a persistence context and have the entity manager for that persistence context acquired automatically.
Listing 3-13 demonstrates using the @PersistenceContext annotation to acquire an entity manager through dependency injection into a stateless session bean. The unitName element specifies the name of the persistence unit on which the persistence context will be based.
If the unitName element is omitted, it is vendor-specific how the unit name for the persistence context is determined. Some vendors can provide a default value if there is only one persistence unit for an application, whereas others might require that the unit name be specified in a vendor-specific configuration file.
Injecting an EntityManager Instance
You might be wondering why a state field exists in a stateless session bean; after all, entity managers must maintain their own state to be able to manage a specific persistence context. The good news is that the specification was designed with container integration in mind, so what actually gets injected in Listing 3-13 is not an entity manager instance like the ones we used in the previous chapter. The value injected into the bean is a container-managed proxy that acquires and releases persistence contexts on behalf of the application code. This is a powerful feature of the Jakarta Persistence API in Jakarta EE and is covered extensively in Chapter 6.
For now, it is safe to assume that the injected value will “do the right thing.” It does not have to be disposed of and works automatically with the transaction management of the application server. Other containers that support Jakarta Persistence, such as Spring, will offer similar functionality, but they will generally require some additional configuration for it to work.
Referencing a Persistence Unit
The EntityManagerFactory for a persistence unit can be referenced using the @PersistenceUnit annotation . Like the @PersistenceContext annotation, the unitName element identifies the persistence unit for the EntityManagerFactory instance we want to access. If the persistent unit name is not specified in the annotation, it is vendor-specific how the name is determined.
Injecting an EntityManagerFactory Instance
The EntityManagerFactory for a persistence unit is not used as often in the Jakarta EE environment because injected entity managers are easier to acquire and use. As you will see in Chapter 6, there are important differences between the entity managers returned from the factory and the ones provided by the server in response to the @PersistenceContext annotation .
Referencing Server Resources
The @Resource annotation is the catch-all reference for Jakarta EE resource types that don’t have dedicated annotations. It is used to define references to resource factories, data sources, and other server resources. The @Resource annotation is also the simplest to define because the only additional element is resourceType, which allows you to specify the type of resource if the server can’t figure it out automatically. For example, if the field you are injecting into is of type Object, then there is no way for the server to know that you wanted a data source instead. The resourceType element can be set to javax.sql.DataSource to make the need explicit.
Injecting a SessionContext Instance
CDI and Contextual Injection
While the basic platform injection facilities are helpful, they are clearly limited both in terms of what can be injected and how much control can be exerted over the injection process. CDI provides a more powerful injection standard that first extends the notion of a managed bean and platform resource injection and then goes on to define a set of additional injection services available to beans managed by CDI. Of course, the key characteristic of contextual injection is the ability to inject a given object instance according to the currently active context.
The capabilities of CDI are broad and extensive, and obviously well beyond the scope of a book on Jakarta Persistence. For the purposes of this book, we only scratch the surface and show how to create and use simple CDI beans with qualifiers. We suggest that interested readers refer to some of the many books written about CDI to find out more about interceptors, decorators, events, and the many other features available within a CDI container.
CDI Beans
One of the benefits of Enterprise Beans is that they provide all of the services that one might need, from security to automatic transaction management and concurrency control. However, the full service model can be seen as a drawback if you don’t use or want some of the services, since the perception is that there is at least some cost associated with having them. Managed beans, and the CDI extensions to them, provide more of a pay-as-you-go model. You only get the services that you specify. Don’t be fooled, though, into thinking that CDI beans are any less bulky than a modern Enterprise Beans. When it comes down to implementation, both types of objects are proxied by the container in pretty much the same way, and the service hooks will be added in and triggered as necessary.
What are CDI beans, anyway? A CDI bean is any class that qualifies for the CDI injection services, the primary requirement of which is simply that it be a concrete class.2 Even session beans can be CDI beans and thus qualify for CDI injection services, although there are some caveats about their lifecycle contexts.
Injection and Resolution
CDI Bean with Field Injection
CDI Bean with Constructor Injection
Scopes and Contexts
Request: Delineated by a specific client method request.
Session: Starts on initiation from an HTTP client and ends on the termination of the HTTP session. It is shared by all requests in the same HTTP session.
Application: Global to the entire application for as long as it is active.
Conversation: Spans a series of sequential Jakarta Faces requests.
Transaction: Maps to the lifetime of an active Jakarta Transactions transaction.
A bean type is associated with a scope by annotating the bean class with the intended scope annotation. Managed instances of that bean will have a lifecycle similar to the declared scope.
Each scope that is active will have a current context associated with it. For example, when a request arrives, a request context will be created for that request scope. Each request will have its own current request context, but there will be a single session scoped context for all of the requests coming from the same HTTP session. The context is just the place where the scoped instances reside for the duration of the scope. There can be only one instance of each bean type in each context.
Because it is application scoped, only a single instance would be created by CDI and reside in the application-scoped context. Note that this is similar to a singleton Enterprise Bean in that only a single instance would be created and used by the entire application.
An additional scope, dependent, also exists, but is really the absence of scope. If no scope is specified, then the dependent scope is assumed, meaning that no instances of the bean are put into any context and a new instance is created each time an injection of that bean type occurs. The @Dependent annotation can be used to explicitly mark a bean as being dependent. Scope annotations are defined in the jakarta.enterprise.context package .
In CDI, beans may be annotated with a bean-defining annotation (i.e., a scope annotation) to preclude the beans.xml descriptor from being required. In the examples that use CDI, we use scope annotations to avoid having to specify a beans.xml file.
Qualified Injection
A qualifier is an annotation that is used to constrain or distinguish a bean type from other bean types that have the same inherited or implemented interface type. A qualifier can help the container resolve which bean type to inject.
Qualifier Annotation Definition
Qualified Bean Class
Qualified Injection
Producer Methods and Fields
When the container needs to inject an instance of a bean type, it will first look in the current contexts to see if one already exists. If it does not find one, then it must obtain or create a new instance. CDI provides a way for the application to control the instance that gets “created” for a given type by using a producer method or field.
A producer method is a method that the CDI container will invoke to obtain a new bean instance. The instance may be instantiated by the producer method, or the producer may obtain it through some other means; it is entirely up to the implementation of the producer method how it produces the instance. Producer methods may even decide based on runtime conditions to return a different bean subclass.
A producer method can be annotated with a qualifier. Then, when an injection site is similarly qualified, the container will call that correspondingly qualified producer method to obtain the instance.
Producer Method
Producer fields work the same way as producer methods except that the container accesses the field to get the instance instead of invoking a method. It is up to the application to ensure that the field contains an instance when the container needs it. You will see an example of using a producer field in the next section.
Using Producer Methods with Jakarta Persistence Resources
Now that you know some of the basics of CDI, it’s time to learn how CDI can be used to help manage Jakarta Persistence persistence units and contexts. You can use a combination of Jakarta EE resource injection with CDI producer fields and qualifiers to inject and maintain your persistence contexts.
Producer Class and Qualifier Annotation Definitions
DeptService Bean with Injected EntityManager Fields
In the majority of our examples, we simply use the @PersistenceContext annotation since it does not involve any additional producer code. It is also supported by Spring and other kinds of Jakarta Persistence containers, so the bean will be more generic.
Transaction Management
More than any other type of enterprise application, applications that use persistence require careful attention to issues of transaction management. When transactions start, when they end, and how the entity manager participates in container-managed transactions are all essential topics for developers using Jakarta Persistence. The following sections lay out the foundation for transactions in Jakarta EE; we revisit this topic in detail again in Chapter 6 as we look at the entity manager and how it participates in transactions. Advanced transaction topics are beyond the scope of this book. We recommend Mastering Java EE 8 Application Development3 for an in-depth discussion on developing Applications.
Transaction Review
Atomicity: Either all the operations in a transaction are successful or none of them are. The success of every individual operation is tied to the success of the entire group.
Consistency : The resulting state at the end of the transaction adheres to a set of rules that define acceptability of the data. The data in the entire system is legal or valid with respect to the rest of the data in the system.
Isolation : Changes made within a transaction are visible only to the transaction that is making the changes. Once a transaction commits the changes, they are atomically visible to other transactions.
Durability : The changes made within a transaction endure beyond the completion of the transaction.
A transaction that meets all these requirements is said to be an ACID transaction (the familiar ACID term being obtained by combining the first letter of each of the four properties).
Not all transactions are ACID transactions, and those that are often offer some flexibility in the fulfillment of the ACID properties. For example, the isolation level is a common setting that can be configured to provide either looser or tighter degrees of isolation than what was described earlier. They are typically done for reasons of either increased performance or, on the other side of the spectrum, if an application has more stringent data consistency requirements. The transactions that we discuss in the context of Jakarta EE are normally of the ACID variety.
Enterprise Transactions in Java
Transactions actually exist at different levels within the enterprise application server. The lowest and most basic transaction is at the level of the resource, which in our discussion is assumed to be a relational database fronted by a DataSource interface . This is called a resource-local transaction and is equivalent to a database transaction. These types of transactions are manipulated by interacting directly with the JDBC DataSource that is obtained from the application server. Resource-local transactions are used less frequently in servers than container transactions.
The broader container transaction uses the Jakarta Transactions API that is available in every compliant Jakarta EE application server and most of the Java-based web servers. This is the typical transaction that is used for enterprise applications and can involve or enlist a number of resources, including data sources as well as other types of transactional resources. Resources defined using Jakarta Connectors components can also be enlisted in the container transaction.
Containers typically add their own layer on top of the JDBC DataSource to perform functions such as connection management and pooling that make more efficient use of the resources and provide a seamless integration with the transaction-management system. This is also necessary because it is the responsibility of the container to perform the commit or rollback operation on the data source when the container transaction completes.
Because container transactions use Jakarta Transactions and because they can span multiple resources, they are also called Jakarta Transactions transactions or global transactions. The container transaction is a central aspect of programming within Java servers.
Transaction Demarcation
Every transaction has a beginning and an end. Beginning a transaction will allow subsequent operations to become a part of the same transaction until the transaction has completed. Transactions can be completed in one of two ways. They can be committed, causing all of the changes to be persisted to the data store, or rolled back, indicating that the changes should be discarded. The act of causing a transaction to either begin or complete is termed transaction demarcation. This is a critical part of writing enterprise applications, because doing transaction demarcation incorrectly is one of the most common sources of performance degradation.
Resource-local transactions are always demarcated explicitly by the application, whereas container transactions can either be demarcated automatically by the container or by using a Jakarta Transactions interface that supports application-controlled demarcation. The first case, when the container takes over the responsibility of transaction demarcation, is called container-managed transaction (CMT) management, but when the application is responsible for demarcation, it’s called bean-managed transaction (BMT) management.
Changing the Transaction-Management Type of an Enterprise Bean
Because the default transaction management for an Enterprise Bean is container-managed, the @TransactionManagement annotation needs to be specified only if bean-managed transactions are desired.
Container-Managed Transactions
The most common way to demarcate transactions is to use CMTs, which spare the application the effort and code to begin and commit transactions explicitly.
Transaction requirements are determined by metadata and are configurable at the granularity of the class or even a method. For example, a session bean can declare that whenever any specific method on that bean gets invoked, the container must ensure that a transaction is started before the method begins. The container is also responsible for committing the transaction after the completion of the method.
It is quite common for one bean to invoke another bean from one or more of its methods. In this case, a transaction started by the calling method will not have been committed because the calling method will not be completed until its call to the second bean has completed. This is why we need settings to define how the container should behave when a method is invoked within a specific transactional context.
For example, if a transaction is already in progress when a method is called, the container might be expected to just use that transaction, whereas it might be directed to start a new one if no transaction is active. These settings are called transaction attributes, and they determine the container-managed transactional behavior.
MANDATORY: If this attribute is specified for a method, a transaction is expected to have already been started and be active when the method is called. If no transaction is active, an exception is thrown. This attribute is seldom used, but can be a development tool to catch transaction demarcation errors when it is expected that a transaction should already have been started.
REQUIRED: This attribute is the most common case in which a method is expected to be in a transaction. The container provides a guarantee that a transaction is active for the method. If one is already active, it is used; if one does not exist, a new transaction is created for the method execution.
REQUIRES_NEW: This attribute is used when the method always needs to be in its own transaction; that is, the method should be committed or rolled back independently of methods further up the call stack. It should be used with caution because it can lead to excessive transaction overhead.
SUPPORTS: Methods marked with supports are not dependent on a transaction, but will tolerate running inside one if it exists. This is an indicator that no transactional resources are accessed in the method.
NOT_SUPPORTED: A method marked to not support transactions will cause the container to suspend the current transaction if one is active when the method is called. It implies that the method does not perform transactional operations, but might fail in other ways that could undesirably affect the outcome of a transaction. This is not a commonly used attribute.
NEVER: A method marked to never support transactions will cause the container to throw an exception if a transaction is active when the method is called. This attribute is very seldom used, but can be a development tool to catch transaction demarcation errors when it is expected that transactions should already have been completed.
Any time the container starts a transaction for a method, the container is assumed to also attempt to commit the transaction at the end of the method. Each time the current transaction must be suspended, the container is responsible for resuming the suspended transaction at the conclusion of the method.
There are actually two different ways of specifying container-managed transactions, one for Enterprise Beans and one for CDI beans, servlets, Jakarta REST resource classes, and all other types of Jakarta EE managed components. Enterprise Beans were the first component type to offer a container-managed transaction feature and defined specific metadata for the purpose. CDI beans and other types of Jakarta EE components, such as servlets or Jakarta REST resource classes, use a transactional interceptor.
Enterprise Beans Container-Managed Transactions
The transaction attribute for an Enterprise Bean can be indicated by annotating the Enterprise Beans class, or one of its methods that is part of the optional business interface, with the @TransactionAttribute annotation . This annotation requires a single argument of the enumerated type TransactionAttributeType , the values of which are defined in the preceding list. Annotating the bean class will cause the transaction attribute to apply to all of the business methods in the class, whereas annotating a method applies the attribute only to the method. If both class-level and method-level annotations exist, the method-level annotation takes precedence. In the absence of class-level or method-level @TransactionAttribute annotations, the default attribute of REQUIRED will be applied.
Specifying an Enterprise Bean Transaction Attribute
Furthermore, before the addItem() method adds the item to the cart, it does some validation in a private method called verifyItem() that is not shown in the example. When this method is invoked from addItem(), it will run in whatever transactional context addItem() was invoked.
Any bean wanting to cause a container-managed transaction to roll back can do so by invoking the setRollbackOnly() method on the EJBContext object. Although this will not cause the immediate rollback of the transaction, it is an indication to the container that the transaction should be rolled back when the transaction completes. Note that entity managers will also cause the current transaction to be set to roll back when an exception is thrown during an entity manager invocation or when the transaction completes.
Transactional Interceptors
Interceptors are opening up the possibility of decoupling the container-managed transaction facility from Enterprise Beans and offering it to any component that supported interception. The @jakarta.transaction.Transactional annotation allows to specify that a transactional interceptor is applied to the target component. The mechanism acts similarly to Enterprise Beans container-managed transactions in that it is declaratively applied on a class or method and a class semantic can be overridden by a method-based one. The main difference is that the @Transactional annotation is used instead of @TransactionAttribute, and the type of the enumerated value is a nested enum called Transactional.TxType instead of the TransactionAttributeType used in Enterprise Beans. These enum constants are named exactly the same in Transactional.TxType and have the same semantics.
Perhaps the most relevant difference between transactional interceptor-based components and Enterprise Beans CMT is the fact that the components that use transactional interceptors do not automatically get CMT but must opt in by using the @Transactional annotation. Enterprise Beans get CMT by default and must opt out if they prefer to use BMT.
Transactional Interceptor in a CDI Bean
@Transactional can be used on managed beans or CDI beans but may not be used on Enterprise Beans. Conversely, @TransactionAttribute can only be used on Enterprise Beans.
Bean-Managed Transactions
Another way of demarcating transactions is to use BMTs. This just means that the application needs to start and stop transactions explicitly by making API calls. With the exception of Enterprise Beans, all managed components default to using bean-managed transactions and are left to do their own transaction demarcation if they don’t specify the @Transactional interceptor . Only Enterprise Beans are provided container-managed transactions by default.
Declaring that an Enterprise Bean is using bean-managed transactions means that the bean class is assuming the responsibility to begin and commit the transactions whenever it deems it is necessary. With this responsibility, however, comes the expectation that the bean class will get it right. Enterprise Beans that use BMT must ensure that any time a transaction has been started, it must also be completed before returning from the method that started it. Failure to do so will result in the container rolling back the transaction automatically and an exception being thrown.
One penalty of transactions being managed by the Enterprise Beans instead of by the container is that they do not get propagated to methods called on another BMT Enterprise Beans. For example, if Enterprise Bean A begins a transaction and then calls Enterprise Bean B, which is using bean-managed transactions, the transaction will not get propagated to the method in Enterprise Bean B. Any time a transaction is active when a BMT Enterprise Bean method is invoked, the active transaction will be suspended until control returns to the calling method. This is not true for non-Enterprise Beans components.
BMT is not generally recommended for use in Enterprise Beans because it adds complexity to the application and requires the application to do work that the server can already do for it. While other types of components can use transactional interceptors if they choose, they do not have the same BMT restrictions that Enterprise Beans have, so it is more common for them to adopt an application-controlled BMT approach. They have also traditionally used BMT because there was never really a choice in the past.
UserTransaction
The UserTransaction Interface
Each Jakarta Transactions transaction is associated with an execution thread, so it follows that no more than one transaction can be active at any given time. So if one transaction is active, the user cannot start another one in the same thread until the first one has committed or rolled back. Alternatively, the transaction can time out, causing the transaction to roll back.
We discussed earlier that in certain CMT conditions, the container will suspend the current transaction. From the previous API, you can see that there is no UserTransaction method for suspending a transaction. Only the container can do this using an internal transaction-management API. In this way, multiple transactions can be associated with a single thread, even though only one can ever be active at a time.
Rollbacks can occur in several different scenarios. The setRollbackOnly() method indicates that the current transaction cannot be committed, leaving rollback as the only possible outcome. The transaction can be rolled back immediately by calling the rollback() method . Alternatively, a time limit for the transaction can be set with the setTransactionTimeout() method , causing the transaction to roll back when the limit is reached. The only catch with transaction timeouts is that the time limit must be set before the transaction starts and it cannot be changed once the transaction is in progress.
In Jakarta Transactions, every thread has a transactional status that can be accessed through the getStatus() call. The return value of this method is one of the constants defined on the jakarta.transaction.Status interface. If no transaction is active, for example, then the value returned by getStatus() will be the STATUS_NO_TRANSACTION. Likewise, if setRollbackOnly() has been called on the current transaction, then the status will be STATUS_MARKED_ROLLBACK until the transaction has begun rolling back.
Using the UserTransaction Interface
Putting It All Together
Now that we have discussed the application component model and services available as part of a Jakarta EE application server, we can revisit the EmployeeService example from the previous chapter and bring it to the Jakarta EE environment. Along the way, we provide example code to show how the components fit together and how they relate back to the Java SE example.
Defining the Component
To begin, let’s consider the definition of the EmployeeService class from Listing 2-9. The goal of this class is to provide business operations related to the maintenance of employee data. In doing so, it encapsulates all the persistence operations. To introduce this class into the Jakarta EE environment, we must first decide how it should be represented. The service pattern exhibited by the class suggests a session bean or similar component. Because the business methods of the bean have no dependency on each other, we can further decide that any stateless bean, such as a stateless session bean, is suitable. In fact, this bean demonstrates a very typical design pattern called a session façade,4 in which a stateless session bean is used to shield clients from dealing with a particular persistence API. To turn the EmployeeService class into a stateless session bean, we need to only annotate it with @Stateless.
The EmployeeService Session Bean
Defining the User Interface
Using the EmployeeService Session Bean from a Servlet
Packaging It Up
In the Jakarta EE environment, many properties required in the persistence.xml file for Java SE can be omitted. In Listing 3-31, you see the persistence.xml file from Listing 2-11 converted for deployment as part of a Jakarta EE application. Instead of JDBC properties for creating a connection, we now declare that the entity manager should use the data source name jdbc/EmployeeDS. If the data source was defined to be available in the application namespace instead of the local component naming context, we might instead use the data source name of java:app/jdbc/EmployeeDS. The transaction-type attribute has also been removed to allow the persistence unit to default to Jakarta Transactions. The application server will automatically find entity classes, so even the list of classes has been removed. This example represents the ideal minimum Jakarta EE configuration.
Defining a Persistence Unit in Jakarta EE
Summary
It would be impossible to provide details on all of the features of the Jakarta EE platform in a single chapter. However, we cannot put Jakarta Persistence in context without explaining the application server environment in which it will be used. To this end, we introduced the technologies that are of the most relevance to the developer using persistence in enterprise applications.
We began with an introduction to enterprise software components and introduced the Enterprise Beans component model. We argued that the use of components is more important than ever before and identified some of the benefits that come from leveraging them. We introduced the fundamentals of stateless, stateful, and singleton session beans and showed the syntax for declaring them as well as the difference in interaction style between them.
We next looked at dependency management in Jakarta EE application servers. We discussed the reference annotation types and how to declare them. We also looked at the difference between dependency lookup and dependency injection. In the case of injection, we looked at the difference between field and setter injection. We then explored each of the resource types, demonstrating how to acquire and inject server and Jakarta Persistence resources.
Building on the foundation of Jakarta EE resource injection, we went on to introduce the CDI model with its generalized notion of a managed bean. We listed the predefined scopes and explained the contexts that CDI uses to cache the contextual instances it injects. We showed how qualifiers can contribute additional constraints to the injection resolution process and how producers can be defined to return the instances the CDI container uses for injection. We then demonstrated a way to use producers to inject qualified persistence resources.
In the section on transaction management, we looked at Jakarta Transactions and its role in building data-centric applications. We then looked at the difference between bean-managed transactions and container-managed transactions for Enterprise Beans and non-Enterprise Beans. We documented the different types of transaction attributes for CMT beans and showed how to manually control bean-managed transactions.
We concluded the chapter by exploring how to use Jakarta EE components with Jakarta Persistence by converting the example application introduced in the previous chapter from a command-line Java SE application to a web-based application running on an application server.
Now that we have introduced Jakarta Persistence in both the Java SE and Jakarta EE environments, it’s time to dive into the specification in detail. In the next chapter, we begin this journey with the central focus of Jakarta Persistence: object-relational mapping.