Тайна наследования Java EE 6 аннотаций

Я использую наследование с EJB в нескольких сценариях, иногда с аннотациями в суперклассе, как этот generic entityDAO:

public class JpaDAO<T>{
    protected Class<T> entityClass;

    @PersistenceContext(unitName="CarrierPortalPU")
    protected EntityManager em;
    protected CriteriaBuilder cb;

    @PostConstruct
    private void init() {
        cb = em.getCriteriaBuilder();
    }

    public JpaDAO(Class<T> type) {
        entityClass = type;
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void create(T entity) {
        em.persist(entity);
    }

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public T find(Object id) {
        return em.find(entityClass, id);
    }

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public List<T> findAll(){
        CriteriaQuery<T> cq = cb.createQuery(entityClass);
        Root<T> entity = cq.from(entityClass);
        cq.select(entity);
        return em.createQuery(cq).getResultList();
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void remove(T entity) {
        em.remove(em.merge(entity));
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public T edit(T entity) {
        return em.merge(entity);
    }

}

С примером подкласса, реализованного следующим образом:

@Stateless
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class DepartmentDAO extends JpaDAO<Department> {

    public DepartmentDAO() {
        super(Department.class);
    }

    public Department findByName(String name){
        CriteriaQuery<Department> cq = cb.createQuery(Department.class);
        Root<Department> department = cq.from(Department.class);
        cq.where(cb.equal(department.get(Department_.name), name));
        cq.select(department);
        try{
            return em.createQuery(cq).getSingleResult();
        }catch(Exception e){
            return null;
        }
    }
}

Недавно я прочитал, что аннотации java не наследуются (источник). Это должно привести к тому, что мой JpaDAO выбросит исключение нулевого указателя при доступе к его сущ. Или его критерию (поскольку оба @PersistanceContext и @PostConstruct будут проигнорированы), однако это не так. Может ли кто-нибудь прояснить, как это работает? Я беспокоюсь о том, что происходит с моими @TransactionAttributes в суперклассе, могу ли я доверять REQUIRED для фактического использования транзакций при вызове из подкласса, когда подкласс имеет NOT_SUPPORTED как класс по умолчанию?

Ответ 1

Аннотации Java не наследуются, но спецификации JavaEE изменяют правила, чтобы эти атрибуты работали должным образом. См. Общие аннотации 1.1 spec. В разделе 2.1 даже используется атрибут @TransactionAttribute. В разделе 13.3.7.1 EJB 3.1 также явно указаны правила для атрибута @TransactionAttribute:

Если класс bean имеет суперклассы, применяются следующие дополнительные правила.

  • атрибут транзакции, указанный в суперклассе S, применяется к бизнес-методам, определенным S. Если атрибут транзакции на уровне класса не указан на S, он эквивалентен спецификации TransactionAttribute (REQUIRED) на S.
  • Атрибут транзакции может быть указан в бизнес-методе M, определяемом классом S, для переопределения для метода M значения атрибута транзакции, явно или неявно указанного в классе S.
  • Если метод M класса S переопределяет бизнес-метод, определенный суперклассом S, атрибут транзакции M определяется приведенными выше правилами применительно к классу S.

Короче говоря, для большинства аннотаций JavaEE аннотации на уровне метода применяются к этому методу, если подкласс не отменяет этот метод, а аннотации на уровне класса применяются ко всем методам, определенным только в этом классе. Правило не применяется к "компиляционным" аннотациям на уровне классов, таким как @Stateless (см. Раздел 4.9.2.1 спецификации EJB 3.1)