Sunday, November 8, 2015

Java EE 7 Platformunda İlgiye Dayalı Programlama

Java EE üzerinde ilgiye dayalı programlama yapmak için farklı katmanlarda farklı çözümler yer alır:
  • Web Filter
         HTTP isteği, sunum katmanına girmeden önce araya girip, güvenlik (kimlik doğrulama, yetkilendirme, kullanıcının tek oturum açabilmesi gibi), kayıt tutmak, başarımı izlenmek gibi farklı ilgileri kodlayabiliriz. Aşağıdaki örnekte, hangi saatte hangi oturumdan hangi kaynağa (xhtml, jsp gibi) erişildiğinin kaydını tutmak için yazılmış bir Web Filtresi yer alıyor. Kodu basit tutmak amacıyla, herhangi bir loglama çatısından (log4j, logback gibi) yararlanılmamıştır. 
package com.example.web.filters;

import java.io.IOException;
import java.util.Date;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

@WebFilter(urlPatterns = "/*")
public class SessionAuditFilter implements Filter {

   @Override
   public void destroy() {

   }

   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    HttpServletRequest _request = (HttpServletRequest) request;
    String pathInfo = _request.getPathInfo();
    if (pathInfo.endsWith(".xhtml") || pathInfo.endsWith(".jsp")) {
       Date now = new Date();
       String sessionId = _request.getSession().getId();
       System.err.println("Access to " + pathInfo 
                              + " for the session " + sessionId 
                              + " @ " + now);
    }
    chain.doFilter(request, response);
   }

   @Override
   public void init(FilterConfig filterConfig) throws ServletException {

   }

}
  • WebListener
WebListener kullanarak, 
  • oturum açıldığında ya da sona erdiğinde (HttpSessionListener), 
  • oturumda saklanmak üzere bir nesne eklendiğinde, güncellendiğinde ya da silindiğinde (HttpSessionAttributeListener), 
  • uygulama başlatıldığında ya da durdurulduğunda (ServletContextListener)
araya girip işler yapmak mümkündür. Aşağıdaki örnekte HttpSessionAttributeListener kullanarak oturuma eklenen nesnelerin boyutu ve bellek yerleşimi ekrana listelenmektedir:
package com.example.web.listeners;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

import org.openjdk.tools.objectlayout.ObjectLayout;

@WebListener
public class SessionAttributeMemoryLayoutListener implements HttpSessionAttributeListener {

   @Override
   public void attributeAdded(HttpSessionBindingEvent event) {
    Object value = event.getValue();
    System.err.println("Attribute is added: " + value);
    printMemoryLayout(value);
   }

   @Override
   public void attributeRemoved(HttpSessionBindingEvent event) {
     Object value = event.getValue();
     System.err.println("Attribute is removed: " + value);
     printMemoryLayout(value);
   }

   @Override
   public void attributeReplaced(HttpSessionBindingEvent event) {
     Object value = event.getValue();
     System.err.println("Attribute is replaced: " + value);
     printMemoryLayout(value);
   }

   private void printMemoryLayout(Object o) {
     try {
       System.err.println("Size: " + ObjectLayout.sizeOf(o));
       ObjectLayout.analyze(System.err, o.getClass());
     } catch (Exception e) {
       e.printStackTrace();
     }
   }
}
  • PhaseListener
           JSF çatısında HTTP istekleri karşılanırken Uç Denetçinin (=Front Controllerjavax.faces.webapp.FacesServlet yönettiği 6 evreden (=Phase) oluşan bir döngü çalışır. Bu evrelerden her biri çalışmadan önce ve çalıştıktan sonra araya girip farklı problemler ile ilgilenen kodlar yazılabilir. Aşağıdaki örnekte bu evrelerin teker teker ve toplam olarak çalışma sürelerini ölçen bir PhaseListener çözümü verilmiştir. Çözümde PerformanceMonitorPhaseListener sınıfı kodlanırken, Thread-Safe olmasını sağlayacak önlemler alınmıştır:
package com.example.jsf;

import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.http.HttpServletRequest;

public class PerformanceMonitorPhaseListener implements PhaseListener {

   @Override
   public void afterPhase(PhaseEvent event) {
      HttpServletRequest request = (HttpServletRequest) 
              event.getFacesContext().getExternalContext().getRequest();
       long start = (Long) request.getAttribute("start");
       Long total = (Long) request.getAttribute("total");
       if (total == null) total = 0L;
       long duration = System.nanoTime() - start;
       total += duration;
       request.setAttribute("total", total);
       String sessionId = request.getSession().getId();
       System.err.println("Session (" + sessionId + ") [" 
                           + event.getPhaseId() + "]: " + duration);
       if (event.getPhaseId().equals(PhaseId.RENDER_RESPONSE)) {
          System.err.println("Session (" + sessionId + ") [TOTAL]: " + total);
       }
   }

   @Override
   public void beforePhase(PhaseEvent event) {
       HttpServletRequest request = (HttpServletRequest) 
              event.getFacesContext().getExternalContext().getRequest();
       request.setAttribute("start", System.nanoTime());
   }

   @Override
   public PhaseId getPhaseId() {
       return PhaseId.ANY_PHASE;
   }

}
  • Context and Dependency Injection (CDI) ile İlgiye Dayalı Programlama
Java EE 6 ile birlikte yeni bir bileşen modelimiz daha var: CDI (Contexts and Dependency Injection) Bean. CDI temel olarak hizmet alan ve veren bileşenler arasındaki bağımlılıkları yöneten bir çatı sunuyor. Tıpkı Spring, Guice, JBoss Seam gibi. Bunun dışında ilgiye dayalı programlama, olay temelli programlama ve dekoratör tasarım kalıbı gibi çözümlere hızlı bir şekilde ulaşmamızı sağlar. İlgiye dayalı programlama için ise önce ilgiyi tanımlayacak bir not (=annotation) tasarlıyoruz:
package com.example.aspects;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.interceptor.InterceptorBinding;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@InterceptorBinding
public @interface Audit {

}
Bu notun (@Audit) metod ve sınıflara düşülebileceğini @Target({ ElementType.METHODElementType.TYPE }) damgasını kullanarak tanımladık. Yürütme zamanında uygulamanın bu notları okuyabilmesi için hem derleyicinin notu class uzantılı dosyaya meta veri olarak yazması hem de sınıf yükleyicinin (=class loader) belleğe PermGen ya da Meta-Space alanına yüklemesi için @Retention(RetentionPolicy.RUNTIME) notunu düştük. Tanımda kullandığımız üçüncü not (@InterceptorBinding) ile bunun CDI çatısında ilgiye dayalı programlama yapmak için tasarlanmış bir not olduğunu belirtiyoruz. Notlar derlendiklerinde çalışan bir koda dönüşmezler, meta bilgi olarak .class dosyasına yazılırlar. Bu nedenle, bu notun iliştirildiği metodlar çalışmadan önce araya girip işler yapmamızı sağlayacak metodu tanımlamalıyız: 
package com.example.aspects;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Date;

import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

@Interceptor
@Audit
public class AuditInterceptor implements Serializable {
   @AroundInvoke
   public Object audit(InvocationContext ic) throws Exception {
       String methodName = ic.getMethod().getName();
       Date now = new Date();
       System.err.println( methodName + " is called at " + now );
       System.err.println( "Parameters are " + Arrays.toString(ic.getParameters()) );
       Object result = ic.proceed();
       now = new Date();
       System.err.println(methodName + " returns " + result + " @ " + now);
       return result;
   }
}
Burada @Interceptor notu, bu sınıfın CDI için bir ilgiye dönük programlama için tasarlanmış  araya girip işler yapacak bir sınıf olduğunu, @Audit notu ise bunu @Audit notu ile damgalanmış metodlar için yapacağını tanımlıyor. AuditInterceptor sınıfının araya girip işler yapacak metodunun (audit) ise @AroundInvoke notu düşüldüğünü görüyoruz. Hangi nesnenin (ic.getTarget()) hangi metodu (ic.getMethod()) çalışmadan önce araya girdiğimize, parametrelerine (ic.getParameters()) ve diğer bilgilere erişmek için ise audit() metoduna parametre olarak gelen InvocationContext tipindeki parametreden yararlanıyoruz. ic.proceed() çağrısı ile ise asıl işi yapacak nesnenin araya girdiğimiz metodunun çalışmasını sağlıyoruz. @Audit notunu bir sınıfa iliştirirsek o sınıfın tüm metodları çalışmadan önce araya girilmesini sağlamış oluruz:
package com.example.stockmarket.viewmodel;

import java.io.Serializable;
import java.util.Collection;

import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;

import com.example.stockmarket.dao.StockDao;
import com.example.stockmarket.entity.Stock;

@Named("viewModel")
@SessionScoped
@Audit
public class StockViewModel implements Serializable {
   @Inject
   transient private StockDao stockDao;
   private Stock stock= new Stock();
   private Collection<Stock> stocks;

   public Stock getStock() {
       return stock;
   }

   public void setStock(Stock stock) {
       this.stock = stock;
   }

   public Collection<Stock> getStocks() {
       return stocks;
   }

   public void setStocks(Collection<Stock> stocks) {
       this.stocks = stocks;
   }

   public void doFind(){ 
       stock= stockDao.find(this.stock.getSymbol());
   }

   public void doFindAll(){
       this.stocks= stockDao.findAll();
   }

   public void doDelete(){
       this.stock= stockDao.delete(stock.getSymbol());
       this.stock= new Stock();
   }

   public void doAdd(){
       stockDao.add(stock);
       this.stock= new Stock();
   }

   public void doUpdate(){
       stockDao.update(stock);
       this.stock= new Stock();
   }

}
Eğer @Audit notunu sadece bir metoda düşersek, o not düştüğümüz metod çalışmadan önce araya girilir:
package com.example.stockmarket.dao.impl;

import java.util.Collection;
import java.util.List;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import com.example.aspects.Audit;
import com.example.stockmarket.dao.StockDao;
import com.example.stockmarket.entity.Stock;

@Stateless
@LocalBean
public class JpaStockDao implements StockDao {
   @PersistenceContext(unitName="stockdbPU")
   private EntityManager em;

   @Override
   public Stock find(String symbol) {  
       return em.find(Stock.class, symbol);
   }

   @Override
   public Collection<Stock> findAll() {
       List<Stock> stocks = 
          em.createNamedQuery("Stock.findAll",Stock.class)
     .getResultList();
       return stocks;
   }

   @Override
   public Stock add(Stock stock) {
       em.persist(stock);
       return stock;
   }

   @Override
   @Audit
   public Stock update(Stock stock) {
       Stock found= em.find(Stock.class, stock.getSymbol());
       if (found==null) return null;
       found.setPrice(stock.getPrice());
       found.setCompany(stock.getCompany());
       return found;
   }

   @Override
   @Audit
   public Stock delete(String symbol) {
       Stock found= em.find(Stock.class, symbol);
       if (found==null) return null;
       em.remove(found);
       return found;
   }

}
Yukarıdaki örnekte sadece update(Stock stock) ve delete(String symbol) metodlarından önce araya girilecektir.
  • Enterprise Java Beans (EJB) Bileşenlerinde İlgiye Dayalı Programlama
Tarihsel nedenlerden dolayı Java EE'de bir kaç tane bileşen modeli ile karşılaşıyoruz:
  i. EJB kabında çalışan ağır sıklet bileşen modeli: EJB
 ii. Web kabında çalışan hafif sıklet bileşen modeli: EJB Lite
iii. Hafif sıklet bileşen modeli: CDI
EJB, (Session Bean ve Message-Driven Bean) CDI ile karşılaştırıldığında ağır siklet bileşenler sunuyor. Bazı ilgiler EJB bileşenlerine dokunmuş olarak geliyor: Kalıcılık ve Hareket Yönetimi (Transaction Management), Yetkilendirme. CDI bileşeleri için yukarıda yazdığımız Interceptor sınıfını aynen EJB bileşenleri için de kullanabiliriz. CDI öncesinde ise Interceptor'ları hangi sınıf ve metodunda araya girmek için kullanacağımızı, @Interceptors notu ile tanımlıyorduk:
@Stateless
@LocalBean
@Interceptors({AuditInterceptor.class,ProfilingInterceptor.class})
public class JpaStockDao implements StockDao {

   @PersistenceContext(unitName="stockdbPU")
   private EntityManager em;
   
   .
   .
   .
 
}
ya da bunu metodlara da uygulayabiliriz:
@Stateless
@LocalBean
public class JpaStockDao implements StockDao {
   @PersistenceContext(unitName="stockdbPU")
   private EntityManager em;
 
   @Override
   @Interceptors(AuditInterceptor.class) 
   public Stock find(String symbol) {  
       return em.find(Stock.class, symbol);
   }

   @Override
   @Interceptors({AuditInterceptor.class,ProfilingInterceptor.class}) 
   public Collection<Stock> findAll() {
       List<Stock> stocks = 
            em.createNamedQuery("Stock.findAll",Stock.class)
       .getResultList();
       return stocks;
   }

   .
   .
   .

}
  • JAX-WS Web Servislerinde İlgiye Dayalı Programlama
Web servisleri dağıtık bir ortamda çeşitli sayıda uygulamanın ya da bileşenin organizasyon içinde ya da organizasyonlar arasında platformdan ve programlama dilinden bağımsız olarak saydam bir şekilde çalıştırılmasına olanak sağlar. Web servisleri üç teknoloji üzerine kuruludur: 
  • Web Services Description Language (WSDL): Web servisi arayüzünün XML tabanlı olarak tanımlanmasını sağlar 
  • Simple Object Access Protocol (SOAP) : Web servislerine erişimi sağlayan XML tabanlı mesajlaşma standardı
  •  Universal Description, Discovery and Integration (UDDI): Web servis kayıtlarının tutulmasını ve aranabilmesini sağlayar. Şirketler hakkında bir kategorizasyon ve katalog bilgisi sağlar. 
Web servislerinde üç farklı rolde düğüm bulunur (Şekil-1):
  • Tüketici
  • Servis Sağlayıcı
  • Servis Kayıt Sunucusu
Şekil-1 Web Servislerinde yer alan düğümler
Bu üç düğüm arasında haberleşmede SOAP standardı kullanılır. Java EE 7 platformunda SOAP Web Servisi gerçeklemek için JAX-WS 2.2'den yararlanıyoruz. JAX-WS'de SOAP mesajı geldiğinde işlenmeden önce, yanıt gönderilmeden önce ya da hata oluşmuş ise hata mesajı (SOAP Fault) gönderilmeden önce araya girip işler yapmak mümkündür. Bunun için Web Servisinin gerçeklemesini verdiğimiz sınıfa @HandlerChain() notunu düşüyoruz:
@WebService
@Stateless
@HandlerChain(file="handlers.xml")
public class ImdbWebService {

   @Inject
   private MovieService movieService;
 
   public Collection<String> findAllGenres(){  
      return movieService.findAllGenres();
   }
 
   public Collection<Movie> findAllMoviesByYearRange(int from,int to){
      return movieService.findAllMoviesByYearRange(from, to);
   }

}
Bu notta, araya girip işler yapacak sınıfları listelediğimiz XML dokümanının adını veriyoruz (file="handlers.xml"):
<?xml version="1.0" encoding="UTF-8"?>
<javaee:handler-chains xmlns:javaee="http://java.sun.com/xml/ns/javaee"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <javaee:handler-chain>
      <javaee:handler>
 <javaee:handler-name>LoggingHandler</javaee:handler-name>
 <javaee:handler-class>com.example.imdb.aop.LoggingHandler</javaee:handler-class>
      </javaee:handler>
   </javaee:handler-chain>
</javaee:handler-chains>
Bu dokümanın, web servisini sağlayan sınıf ile aynı pakette olması gerekir. XML dokümanında tanıtılan LoggingHandler sınıfının ise SOAPHandler arayüzünü gerçeklemesi gerekir:
package com.example.imdb.aop;

import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.ProtocolException;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

public class LoggingHandler implements SOAPHandler<SOAPMessageContext> {

 @Override
 public boolean handleMessage(SOAPMessageContext mc) {
  // mc.getMessage().getSOAPHeader();
  // mc.getMessage().getSOAPBody();
  return true;
 }

 @Override
 public boolean handleFault(SOAPMessageContext context) {
  return true;
 }

 @Override
 public void close(MessageContext context) {
 }

 @Override
 public Set<QName> getHeaders() {
  return Collections.emptySet();
 }

}
LoggingHandler sınıfında, SOAP mesajı geldiğinde ve yanıt dönüldüğünde handleMessage metodu çalışır. Bu metod parametre olarak SOAPMessageContext tipinden bir parametre alır. Bu parametreyi kullanarak SOAP mesajının başlığına (mc.getMessage().getSOAPHeader()), gövdeye (mc.getMessage().getSOAPBody()) ve diğer alanlarına okumak ya da değiştirmek amacıyla erişilebilir. Bu metod true dönerse, zincirde sıradaki sınıfın handleMessage metodu çalıştırılır ya da en nihayetinde servis çalıştırılır. Aksi halde işlem durdurulur ve giriş mesajı yanıt olarak dönülür.
  • JPA'da İlgiye Dayalı Programlama
Java EE platformunda kalıcılık probleminin çözümü için JDBC ya da JPA kullanıyoruz. JDBC alt düzey bir API olduğu için basit karmaşıklıktaki kalıcılık işlemleri için bile çok fazla tekrar kod yazmayı gerektirir. Üstelik bu kod veritabanı ile ilgili çok fazla detay barındırır, bu da kodun veritabanındaki değişikliklere karşı kırılgan olmasına neden olur. Bu kırılganlık azaltılmak istenirse daha da fazla kod yazmak gerekecektir. JPA ise bu problemi tabloları Java sınıflarına karşı düşürerek çözmeye çalışır. Karşı düşürülen sınıfa Entity adını veriyoruz. Veritabanındaki tablolar Java sınıflarına, bu tabloların sütunları sınıfın özniteliklerine ve tablolar arasındaki çeşitli türden ilişkileri (1-1, 1-N, M-1, M-N) Java sınıfları arasındaki ilişkilere karşı düşürüldüğünde, JPA'nın EntityManager sınıfını kullanarak tüm kalıcılık problemlerini çözebiliriz. Karşı düşürmeyi notları kullanarak Entity sınıfında gerçekliyoruz. İstenirse bu işlem bir XML dokümanı ile de sağlanabilir. Anahtar alana göre arama yapmak için find() metodunu, Entity nesnesini eklemek için persist() metodunu, Entity nesnesini güncellemek için merge() metodunu ve Entity nesnesini silmek için ise remove() metodunu kullanmamız yeterli olur. Tüm bu metodlar (persist, merge ve remove) parametre olarak eklenecek, güncellenecek ve silinecek Entity nesnesini alırlar. Örnek bir dönüşüm için bir alan ve aynı zamanda @Entity sınıfı olan Stock sınıfını kullanacağız:
package com.example.stockmarket.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.Min;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

@Entity
@Table(name = "stocks")
@NamedQueries({ 
   @NamedQuery(name = "Stock.findAll", query = "select s from Stock s") 
})
public class Stock extends BaseEntity implements Serializable {

   @Id
   @Pattern(regexp="^[a-zA-Z]{3,5}$",message="This is not a valid stock symbol!")
   private String symbol;

   @Size(min=8,message="Company name must be larger than or equal to 8!")
   @Column(name = "COMPANY_NAME")
   private String company;

   @Min(value=1,message="Price can not be smaller than 1!")
   private double price;
 
   public Stock() {
   }

   public Stock(String symbol, String company, double price) {
       this.symbol = symbol;
       this.company = company;
       this.price = price;
   }

   public String getSymbol() {
       return symbol;
   }

   public void setSymbol(String symbol) {
       this.symbol = symbol;
   }

   public String getCompany() {
       return company;
   }

   public void setCompany(String company) {
       this.company = company;
   }

   public double getPrice() {
       return price;
   }

   public void setPrice(double price) {
       this.price = price;
   }

}
Tüm Entity sınıflarını @Entity notunu düşüyoruz, ayrıca anahtar alanın hangi öz niteliğe karşı düştüğünü @Id notu ile tanımlamalıyız. Bu iki zorunlu not dışında, dönüşümde kullanılabilecek pek çok sayıda seçimlik not bulunuyor. Her Entity sınıfında hangi tarihte yaratıldığını ve güncellendiğini gösteren alanlar bulunması istenirse, bu alanları her Entity sınıfında tekrar etmek yerine bir alt sınıfta tanımlamayı ve tüm Entity sınıflarını bu alt sınıftan türetmeyi tercih ederiz:
package com.example.stockmarket.entity;

import java.util.Date;

import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@MappedSuperclass
public class BaseEntity {

   @Temporal(TemporalType.TIMESTAMP)
   private Date created;

   @Temporal(TemporalType.TIMESTAMP)
   private Date updated;

   public Date getCreated() {
       return created;
   }

   public void setCreated(Date created) {
       this.created = created;
   }

   public Date getUpdated() {
       return updated;
   }

   public void setUpdated(Date updated) {
       this.updated = updated;
   }

}
Şimdi, bir problemimiz var: Bu tarihleri kim dolduracak? Bu problemin çözümü için EntityListener'dan yararlanıyoruz. EntityListener'lar kayıt eklenmeden ya da kayıt güncellenmeden hemen önce araya girip, bu tür işleri yapacak kodları yazmamızı sağlar. Bu sınıflar sıradan Java sınıflarıdır. Tek özellikleri içinde yaşam döngüsü notları düşülmüş metodlar barındırmalarıdır:
  • @PrePersist
  • @PostPersist
  • @PreUpdate
  • @PostUpdate
  • @PreRemove
  • @PostRemove
Şimdi, bu tarihleri oluşturacak bir EntityListener yazalım:
package com.example.stockmarket.entity.listeners;

import java.util.Date;

import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;

import com.example.stockmarket.entity.BaseEntity;

public class AutoDateListener {

   @PrePersist
   public void initializeDates(BaseEntity entity) {
       Date now = new Date();
       entity.setCreated(now);
       entity.setUpdated(now);
   }

   @PreUpdate
   public void update(BaseEntity entity) {
       Date now = new Date();
       entity.setUpdated(now);
   }

}
Son olarak bu EntityListener'ın kullanmasını sağlayacak tanımlamayı Entity sınıfımıza (Stock.java) bir not (@EntityListeners) olarak ekleyelim:
@Entity
@Table(name = "stocks")
@NamedQueries({ 
   @NamedQuery(name = "Stock.findAll", query = "select s from Stock s") 
})
@EntityListeners(AutoDateListener.class)
public class Stock extends BaseEntity implements Serializable {

   .
   .
   .

}
Nota parametre olarak, içinde Entity yaşam döngüsü notları düşülmüş metodları bulunan sınıfı veriyoruz. EE 7 daha fazla bilgi edinmek isteyenler için Developing Enterprise Applications on Java EE 7 eğitimini tavsiye ederim.

No comments:

Post a Comment