Ch11 Advanced Topics - skatscher/pro_jpa2_book GitHub Wiki

SQL Queries

  • JPQL is the preferred way for querying over entities, though native SQL queries are also supported by JPA. JPQL will never be able to fully encompass all SQL features.
  • SQL features not supported by JPQL (2.0): Inline views (subqueries in the FROM clause), hierarchical queries, access to stored procedures, additional function expressions to manipulate date and time values
  • sometimes to achieve the performance required it is necessary to use a hand-optimized SQL and not JPQL

Defining and executing SQL Queries

  • SQL queries may be defined dynamically at runtime or named in persistence unit metadata
  • dynamically:
    • use createNativeQuery() method of the EntityManager interface, passing the query string and the entity type that will be returned.
    • The query engine uses the ORM of the entity to figure out which result column aliases map to which entity properties. As each row is processed, the query engine instantiates a new entity instance and sets the available data into it. If column aliases of the query do not match up exactly with the ORM for the entity, SQL result set mapping metadata is required.
@Stateless
public class OrgStructureBean implementes OrgStructure {
  private static final String ORG_QUERY = 
    "SELECT emp_id, name, salary, manager_id, dept_id, address_id " +
    "FROM emp " +
    "START WITH manager_id = ? " +
    "CONNECT BY PRIOR emp_id = manager_id";

  @PersistenceContext(name="EmployeeService")
  EntityManager em;

  public List findEmployeesReportingTo(int managerId) {
    return em.createNativeQuery(ORG_QUERY, Employee.class)
             .setParameter(1, managerId)
             .getResultList();
  }
}
  • named:
    • use @NamedNativeQuery annotation to define named SQL queries. This annotation may be placed on any entity and defines the name of the query (must be unique within the persistence unit) as well as the query text.
    • resultClass element: indicates the entity class if the result type is an entity
    • resultSetMapping element: specify the mapping name if the result requires SQL mapping
    • use createNamedQuery() method of EntityManager interface to create and execute the query
@NamedNativeQuery(
  name="orgStructureReportingTo",
  query="SELECT emp_id, name, salary, manager_id, dept_id, address_id " +
        "FROM emp " +
        "START WITH manager_id = ? " +
        "CONNECT BY PRIOR emp_id = manager_id",
  resultClass=Employee.class)
@Stateless
public class OrgStructureBean implements OrgStructure {
  @PersistenceUnit(unitName="EmployeeService")
  EntityManager em;

  public List<Employee> findEmployeesReportingTo(int managerId) {
    return em.createNamedQuery("orgStructureReportingTo", Employee.class)
             .setParameter(1, managerId)
             .getResultList();
  }
}
  • DML statements INSERT, UPDATE, DELETE: query creation with createNativeQuery() and execute with executeUpdate()
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class LoggerBean implements Logger {
  public static final String INSERT_SQL =
    "INSERT INTO message_log (id, message, log_dttm) " + 
    "VALUES(id_seq.nextvak, ?, SYSDATE)";
  
  @PersistenceContext(unitName="Logger")
  EntityManager em;

  public void logMessage(String message) {
    em.createNativeQuery(INSERT_SQL)
      .setParameter(1, message)
      .executeUpdate();
  }
}

Lifecycle Callbacks

  • Every Entity has the potential to go through one or more of a defined set of lifecycle events.
  • In order to respond to any one or more of these events, an entity class or any of its superclasses may declare one or more methods that will be invoked by the provider when the event gets fired. These methods are called callback methods.

Lifecycle Events

  • event types: persisting, updating, removing, loading which correspond to basic database operations
  • each event type, except for loading, has a Pre event and a Post event.

PrePersist and PostPersist

  • The PrePersist event notifies an entity when EnityManager.persist() has been successfully invoked on it, or a new entity has been merged by EntityManager.merge()
  • If the PERSIST cascade option is set on a relationship of an object that has been persisted and the target object is also a new object, the PrePersist event is triggered on the target object.
  • PostPersist events occur when an entity is inserted. Firing of PostPersist event does not indicate that entity has committed successfully to the database, because the transaction may rollback after PostPersist.

PreRemove and PostRemove

  • When an EntityManager.remove() call is invoked on an entity, the PreRemove callback is triggered.
  • Any related entities across relationships that have been configured with the REMOVE cascade option will also get a PreRemove notification.
  • When the SQL for deletion of an entity finally does get sent to the database, the PostRemove event will get fired. The PostRemove event does not guarantee success as the enclosing transaction may still be rolled back.

PreUpdate and PostUpdate

  • Updates to mangages entities may occur at any time, either within a transaction or outside a transaction.
  • The PreUpdate callback is guaranteed to to be invoked only at some point before the database update. Depending on the provider implementation this is done eagerly of lazily.

PostLoad

  • The PostLoad callback occurs after the data for an entity is read from the database and the entity instance is contructed.
  • It can also happen as result of a refresh() call on the entity manager. When a relationship is set to cascade REFRESH, the entities that get cascaded to will also get loaded.

Callback Methods

  • Designating a method as a callback method involves two steps:
    • defining the method according to a given signature
    • annotating the method with the appropriate lifecycle event annotation
  • Required signature defintion:
    • callback method may have any name
    • must take no parameters and must return void
    • final or static methods are not valid
    • checked exceptions may not be thrown. Runtime exceptions may be thrown - if they are thrown while in a transaction, the transaction gets marked for rollback and subsequent lifecycle event invocations are abandoned.
  • Annotations match the names of the events: @PrePersist, @PostPersist, @PreUpdate, @PostUpdate, @PreRemove, @PostRemove, @PostLoad.
  • A method may be annotated with multiple lifecycle event annotations, but only one lifecycle annotation of a given type may be present in an entity class.
  • Not supported types of operations inside a callback method:
    • invoking methods on an entity manager or executing queries
    • accessing other entities
  • Supported types of operations inside a callback method:
    • looking up resources in JNDI
    • using JDBC and JMS resources
    • looking up and invoking EJB session beans
  • Example:
@Entity
public class Employee {
  @Id private int id;
  private String name;
  @Transient private long syncTime;

  @PostPersist
  @PostUpdate
  @PostLoad
  private void resetSyncTime() {
    syncTime = System.currentTimeMillis();
  }

  public long getCachedAge() {
    return System.currentTimeMillis() - syncTime;
  }
}

Entity Listeners

  • Use entity listener, if you want to pull the event handling code out of the entity class into a different class.
  • An entity listener can define one or more callback methods.
  • The entity listener needs typically access to the entiy state. The entity gets passed to the callback method. For that reason the signature of callback methods needs to define a single parameter wthich is compatible to the entity type (the entity class, a superclass, or an interface implemented by the entity)
  • further the callback methods
    • must be public void
    • must be annotatd with the necessary event annotations
    • must have a no-arg constructor
    • should be stateless and declare no fields

Attaching Entity Listeners to Entities

  • Use of @EntityListeners annotation, where one or more listeners may be listed in the annotation.
  • When a lifecycle event occurs, the provider will iterate though each of the entity listeners in the order in which they are listed and instantiates an instance of the entiy listener class that has a method annotated which the annotation for the given event and invoke the callback method. If any of the listeners throws an exception, it will abort the callback process, causing the remaining listeners and the callback method on the entiy to not be invoked.
  • Example
@Entity
@EntityListeners({EmployeeDebugListener.class, NameValidator.class})
public class Employee implements NamedEntity {
  @Id private int id;
  private String name;
  @Transient private long syncTime;
  
  public String getName() { return name; }

  @PostPersist
  @PostUpdate
  @PostLoad
  private void resetSyncTime() {
    syncTime = System.currentTimeMillis();
  }

  public long getCachedAge() {
    return System.currentTimeMillis() - syncTime;
  }
}

public interface NamedEntity {
  public String getName();
}

public class NameValidator {
  static final int MAX_NAME_LEN = 40;

  @PrePersist
  public void validate(NamedEntity obj) {
    if (obj.getName().length()  > MAX_NAME_LEN)
      throw new ValidationException("Identifier out of range");
  }
}

public class EmployeeDebugListener {
  @PrePersist
  public void prePersist(Employee emp) {
    System.out.println("Persist on employee id: " + emp.getId());
  }
  @PreUpdate public void preUpdate(Employee emp) { ... }
  @PreRemove public void preRemove(Employee emp) { ... }
  @PostLoad public void postLoad(Employee emp) { ... }
}

Default Entity Listeners

  • One or more default entity listeners may be declared in the persistence unit metadata. These will apply to all entities in he persistence unit.
  • Any entity may opt out of having the default entiy listeners applied to it by using the @ExcludeDefaultListeners annotation.

Inheritance and Lifecycle Events

Inheriting Callback methods

  • Callback methods may occur in any entity or mapped superclass, be it abstract or concrete.
  • Order of invokation of callback methods: in the order according to its place in the inheritance hierarchy, most general classes first.

Inheriting Entity Listeners

  • @EntityListeners annotation is valid on entities and mapped superclasses in the hierarchy, be it concreate or abstract. It is additive and does not redefine listeners.
  • order of invokation: the listeners listed in the entity superclass annotation get invoked before the listeners in the subclass.
  • With @ExcludeSuperclassListeners annotation entity listeners of all superclasses will not be invoked.

Validation

  • In Java EE 6, validation is considered a separate aspect of the application. It is standardized in JSR303 for the platform. It was also designed to function in a stand-alone Java SE environment.
  • Validation has an annotation model and a dynamic API that can be invoked from any layer and on almost any bean

Using Constraints

  • Adding contraints to an object by annotating the class, its fiels, is its JavaBean-style properties.
  • Validation contraints are defined in the javax.validation.contraints package.
public class Employee {
  @NotNull
  private int id;

  @NotNull(message="Employee name must be specified")
  @Size(max=40)
  private String name;

  @Past
  private Date startDate;
}

Invoking Validation

  • The main API for invoking validation on an object is the javax.validation.Validator class. Passing the object to validate to the validate() method of the Validator instance.
  • A Validator instance may beb injected in any Java EE component that supports injection.
@Stateless
public class EmployeeOperationsEJB implements EmployeeOperations {
  @Resource 
  Validator validator;

  public void newEmployee(Employee emp) {
    validator.validate(emp);
  }
}
  • In a non-container environment, a Validator instance is obtained from a javax.validation.ValidatorFactory.
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
validator.validate(emp);
  • When the validate method fails and a contraint is not satisfied, a ValidationException is thrown with a accompanying message String.

Concurrency

  • A managed Entity belongs to one persistence context and should not be managed by more than one persistence context at any given time.
  • Enitity managers and the persistence contexts that they manage are not intended to be accessed by more than one concurrently executing thread.

Refreshing Entity State

  • The refresh() method of the EnitityManager interface can be used to refresh the entity, when there are changes in the database we do not have in our managed entity.
em.refresh(emp);
  • refresh() can only be used with managed entities. If it is not managed an IllegalArgumentException will be thrown.
  • refresh() works like an "undo", it overwrites the managed entity with the state in the database. Unsaved changes get lost.
  • Refresh operations may be cascaded across relationships with the REFRESH value included in the cascade element.
@ManyToOne(cascade={CascadeType.REFRESH})
private Employee manager;

Locking

Optimistic Locking

  • To use when there is a good chance that the transaction in which changes are made to an entity will be the only one that actually changes the entity in that interval.
  • The lock on the entity is not aquired until the change is actually made to the database, usually at the end of the transaction.
  • When the lock is acquired a check is made on the data in the database. If a change occured meanwhile, it means that the current transaction should not overwrite the changes from the intervening transaction. At this stage, it must rollback the transaction and throw a OptimisticLockException.

Versioning

  • entity has a dedicated persistent field or property declared in it to store the version number of the entity
  • provider can check the version number stored in the database to see if it matched the version that it obtained previously. If it is greater, somebody changes the entity meanwhile and an exception should be thrown.
  • version fields are defined by annotating the field or property on the entity with a @Version annotation. Types can be int, short, long, the corresponding wrapper types and java.sql.Timestamp.
@Version
private int version;
  • It cannot be portably relied on that bulk updates automatically update the version field. Some vendors support it, for those who don't the version can be manually updated as part of the UPDATE statement.
  • version fields are only automatically updated for non-relationship fields or the owning foreign key relationship fields.

Advanced Optimistic Locking Modes

  • By default JPA assumes Read Committed isolation. It guarantees that any changes made inside a transaction will not be visible to other transactions until the changing transaction has been commiteed. (version locking works with Read Committed isolation)
  • Locking options can be specified by means of a number of different calls (each method must be invoked within a transaction)
    • EntityManager.lock() - Explicit method for locking of objects already in persistence context
    • EntityManager.refresh() - Permits a lock mode to be passed in and applies to the object in the persistence context beeing refreshed
    • EntityManager.find() - Permits a lock mode to be passed in and applies to the object beeing returned
    • Query.setLockMode() - Sets the lock mode to be in effect during execution of the query

Optimistic Read Locking

  • Repeatable Read isolation prevents the non-repeatable read anomaly. This is when a transaction queries for the same data twice in the same transaction, the second query returns a different version of the data than was returned in the first because another transaction modified it in the intervening time. Repeatable Read isolation level means that once a transaction has accessed data and another transaction modifies that data, at least one of the transaction must be prevented from committing (which one fails depends on implementation).
  • Optimistic read lock in JPA provides Repeatable Read isolation. To optimistically read-lock an entity, a lock mode of LockModeType.OPTIMISTIC can be passed to one of the locking methods. (LockModeType.OPTIMISTIC was introduced in JPA 2.0 and is really just a rename of LockModeType.READ that existed in JPA 1.0)

Optimistic Write Locking

  • The optimistic write lock guarantees all that the optimistic read lock does, but also pledges to increment the version field in the transaction regardless of whether a user updated the entity or not. This is equivalent of making a forced update to the entity ans this is why the option is called optimistic force increment.
  • To optimistically write lock and enitity a lock mode of LockModeType.OPTIMISTIC_FORCE_INCREMENT can be passes to one of the locking methods. (LockModeType.OPTMISTIC_FORCE_INCREMENT was introduced in JPA 2.0 and is really just a rename of LockModeType.WRITE that existed in JPA 1.0)
  • Updates to the version column do not normally occur when changes are made to a non-owned relationship. Due to this, the common case for using OPTIMISTIC_FORCE_INCREMENT is to guarantee consistency across relationship changes when in the entity relationship pointers change, but in the data model no columns in the entity table change.
⚠️ **GitHub.com Fallback** ⚠️