Skip to content

6. Sürüm 2: OpenEJB / JPA mimarisi

6.1. Portlama ilkelerine giriş

Burada, bir JPA / Spring / Hibernate uygulamasının bir JPA / OpenEJB / EclipseLink uygulamasına taşınmasını yönlendirecek ilkeleri sunuyoruz. Maven projelerini oluşturmak için 6.2. paragrafa geçeceğiz.

6.1.1. İki mimari

Spring / Hibernate ile mevcut uygulama

OpenEJB / EclipseLink ile oluşturulacak uygulama

6.1.2. Projelerin kütüphaneleri

  • [DAO] ve [metier] katmanları artık Spring tarafından örneklenmiyor. Bunlar, OpenEJB konteyneri tarafından örnekleniyor.
  • Spring konteynerinin kütüphaneleri ve yapılandırması kaldırılır ve yerine OpenEJB konteynerinin kütüphaneleri ve yapılandırması gelir.
  • JPA / Hibernate katmanındaki kütüphaneler, JPA / EclipseLink katmanındaki kütüphanelerle değiştirilir.

  • JPA katmanını yapılandıran [META-INF/persistence.xml] dosyası şu şekilde olur:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="pam-openejb-ui-metier-dao-jpa-eclipselinkPU" transaction-type="JTA">
     <!-- varlıklar JPA -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
     <!-- JPA sağlayıcısı EclipseLink'tir -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
     <!-- sağlayıcı özellikleri -->
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • 3. satır: EJB kapsayıcısındaki işlemler, JTA türündedir (Java İşlemi API). Spring ile bu işlemler RESOURCE_LOCAL türündeydi.
  • 9. satır: Kullanılan JPA uygulaması, EclipseLink'tir
  • 5-7. satırlar: JPA katmanı tarafından yönetilen varlıklar
  • 11-13. satırlar: EclipseLink sağlayıcısının özellikleri
  • 12. satır: Her çalıştırmada tablolar oluşturulacaktır

OpenEJB konteyneri tarafından kullanılan JTA veri kaynağının JDBC özellikleri, aşağıdaki [conf/openejb.conf] yapılandırma dosyası ile belirlenecektir:

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<openejb>
  <Resource id="Default JDBC Database">
    JdbcDriver com.mysql.jdbc.Driver
    JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
    UserName root
  </Resource>
</openejb>
  • 3. satır: Uygulamanın içine gömülü (embedded) bir OpenEJB konteyneriyle çalışırken Default JDBC Database kimliği kullanılır.
  • 5. satır: MySQL [dbpam_eclipselink] veritabanını kullanıyoruz

6.1.4. [DAO] katmanının EJB ile uygulanması

  • [DAO] katmanını uygulayan sınıflar, EJB haline gelir. [CotisationDao] sınıfını örnek olarak alalım:

Spring sürümündeki [ICotisationDao] arayüzü şöyleydi:

package dao;

import java.util.List;
import jpa.Cotisation;

public interface ICotisationDao {
   // yeni bir aidat oluştur
  Cotisation create(Cotisation cotisation);
   // mevcut bir üyelik bilgisini düzenle
  Cotisation edit(Cotisation cotisation);
   // mevcut bir katkı payını sil
  void destroy(Cotisation cotisation);
   // belirli bir katkı payını ara
  Cotisation find(Long id);
   // tüm Katkı Payı nesnelerini al
  List<Cotisation> findAll();

}

EJB, bu arayüzü iki farklı biçimde (yerel ve uzak) uygulayacaktır. Yerel arayüz, aynı JVM içinde çalışan bir istemci tarafından kullanılabilir; uzak arayüz ise başka bir JVM içinde çalışan bir istemci tarafından kullanılabilir.

Yerel arayüz:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Local;

@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}
  • 6. satır: [ICotisationDaoLocal] arayüzü, [ICotisationDao] arayüzünden miras alarak tüm yöntemlerini devralır. Yeni yöntemler eklemez.
  • 5. satır: @Local ek açıklaması, onu EJB için yerel bir arayüz haline getirir; EJB bu arayüzü uygulayacaktır.

Uzak arayüz:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Remote;

@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}
  • 6. satır: [ICotisationDaoRemote] arayüzü, tüm yöntemlerini devralmak üzere [ICotisationDao] arayüzünden miras alır. Yeni yöntem eklemez.
  • 5. satır: @Remote anotasyonu, onu uygulayacak olan EJB için bu arayüzü uzak bir arayüz haline getirir.

[DAO] katmanı, her iki arayüzü de uygulayan bir EJB tarafından uygulanır (bu zorunlu değildir):

1
2
3
4
5
6
@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {

  @PersistenceContext
  private EntityManager em;
  • 1. satır: Sınıfı bir EJB yapan @Stateless anotasyonu
  • 2. satır: Sınıfın her yönteminin bir işlem içinde yürütülmesini sağlayan @TransactionAttribute anotasyonu.
  • 5. satır: @PersistenceContext anotasyonu; bu anotasyon, [CotisationDao] sınıfına JPA katmanından EntityManager'i enjekte eder. Bu, Spring sürümünde kullandığımızla aynıdır.

[DAO] katmanının yerel arayüzü kullanıldığında, bu arayüzün istemcisi aynı JVM içinde çalışır.

Yukarıda, [metier] ve [DAO] katmanları nesneleri referans yoluyla paylaşmaktadır. Bir katman paylaşılan nesneyi değiştirdiğinde, diğer katman bu değişikliği görür.

[DAO] katmanının uzak arabirimi kullanıldığında, bu arabirimin istemcisi genellikle başka bir JVM içinde çalışır.

Yukarıda, [metier] ve [DAO] katmanları nesneleri değer olarak değiş tokuş eder (değiş tokuş edilen nesnenin serileştirilmesi). Bir katman paylaşılan bir nesneyi değiştirdiğinde, diğer katman bu değişikliği ancak değiştirilen nesne kendisine geri gönderilirse görebilir.

6.1.5. [metier] katmanının bir EJB tarafından uygulanması

  • [metier] katmanını uygulayan sınıf da, yerel ve uzak bir arayüzü uygulayan bir EJB haline gelir. İlk [IMetier] arayüzü şöyledir:
package metier;

import java.util.List;
import jpa.Employe;

public interface IMetier {
   // maaş bordrosunu almak
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // çalışan listesi
  List<Employe> findAllEmployes();
}

Önceki arayüzden bir yerel arayüz ve bir uzak arayüz oluşturulur:

1
2
3
4
5
6
7
package metier;

import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{
}
1
2
3
4
5
6
7
package metier;

import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{
}

[metier] katmanındaki EJB, bu iki arayüzü uygular:

@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {

   // yerel katmanlar hakkında bilgi [DAO]
  @EJB
  private ICotisationDaoLocal cotisationDao = null;
  @EJB
  private IEmployeDaoLocal employeDao = null;
  @EJB
  private IIndemniteDaoLocal indemniteDao = null;
  • 1-2. satırlar: Her yöntemi bir işlem içinde yürütülen bir EJB tanımlar.
  • 7. satır: EJB'in yerel arayüzü olan [CotisationDao]'e bir referans.
  • 6. satır: @EJB ek açıklaması, EJB kapsayıcısından EJB [CotisationDao]'in yerel arayüzüne bir referans enjekte etmesini ister.
  • 8-11. satırlar: Aynı işlem, EJB, [EmployeDao] ve [IndemniteDao]'in yerel arayüzleri için de tekrarlanır.

Sonuç olarak, EJB ve [Metier] örneklendiğinde, 7., 9. ve 11. satırlardaki alanlar, [DAO] katmanındaki üç EJB'in yerel arayüzlerine yapılan referanslarla başlatılacaktır. Dolayısıyla burada, [metier] ve [DAO] katmanlarının aynı JVM içinde çalışacağı varsayılmaktadır.

6.1.6. EJB'in müşterileri

Yukarıdaki şemada, [metier] katmanıyla iletişim kurmak için, [ui] katmanının, [metier] katmanının EJB uzaktaki arayüzüne ilişkin bir referans alması gerekir.

Yukarıdaki şemada, [metier] katmanıyla iletişim kurmak için, [ui] katmanının, [metier] katmanındaki EJB'in yerel arayüzüne ilişkin bir referans alması gerekir. Bu referansları alma yöntemi, konteynerden konteynere farklılık gösterir. OpenEJB konteyneri için şu şekilde ilerlenebilir:

Yerel arayüzdeki referans:

1
2
3
4
5
6
7
8
9
     // gömülü Open EJB konteynerini yapılandırma
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // JNDI bağlamının önceki özelliklerle başlatılması
    InitialContext initialContext = new InitialContext(properties);
     // DAO katmanlarının örneklenmesi
    employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
  • 2-5. satırlar: OpenEJB konteyneri başlatılır.
  • 5. satır: JNDI (Java Naming and Directory Interface) bağlamı, EJB'lere ilişkin referansları elde etmemizi sağlar. Her bir EJB, JNDI adıyla tanımlanır:
  • (devam)
    • yerel arayüz için EJB adının başına Local eklenir (7-9. satırlar)
    • uzak arayüz için EJB adının başına "Remote" eklenir

Java EE 5 ile bu kurallar, EJB konteynerine göre değişir. Bu bir zorluktur. Java EE 6, tüm uygulama sunucularında taşınabilir bir JNDI notasyonu getirmiştir.

Yukarıdaki kod, EJB yerel arayüzlerine, adları JNDI aracılığıyla referanslar alır. Daha önce, bunların @EJB anotasyonu yoluyla da elde edilebileceğini belirtmiştik. Dolayısıyla şöyle yazmak isteyebiliriz:

@EJB
private IemployeDaoLocal employeDaoLocal ;

@EJB anotasyonu, yalnızca EJB konteyneri tarafından yüklenen bir sınıfa aitse geçerli olur. Örneğin, [Metier] sınıfı için durum böyledir. Yukarıdaki kod ise, EJB konteyneri tarafından yüklenmeyecek bir konsol sınıfına ait olacaktır. Bu nedenle, JNDI veya EJB isimlerini kullanmak zorundayız.

Aşağıda, EJB ve [Metier]'in uzak arayüzüne bir referans elde etmek için gerekli kod yer almaktadır:

1
2
3
4
5
6
7
8
     // gömülü Open EJB konteyneri yapılandırılıyor
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // EJB konteynerinin JNDI bağlamının başlatılması
    InitialContext initialContext = new InitialContext(properties);

     // uzak iş katmanının örneklenmesi
metier = (IMetierRemote) initialContext.lookup("MetierRemote");

6.2. Uygulamalı Çalışma

NetBeans Spring / Hibernate uygulamasını OpenEJB / EclipseLink mimarisine taşımayı planlıyoruz.

Spring / Hibernate ile mevcut uygulama

OpenEJB / EclipseLink ile oluşturulacak uygulama

Varsa, MySQL [dbpam_eclipselink] veritabanını oluşturun. Varsa, tüm tablolarını silin. 6.2.1. paragrafında açıklandığı gibi bu veritabanına bir NetBeans bağlantısı oluşturun.

6.2.2. NetBeans projesinin ilk yapılandırması

  • Maven projesi [mv-pam-spring-hibernate]'i yükleyin
  • Yeni bir Maven Java projesi oluşturun: [mv-pam-openejb-eclipselink] ve [1]
  • [Files] [2] sekmesinde, projenin kök dizinine [conf] [3] adlı bir klasör oluşturun
  • bu klasöre aşağıdaki [openejb.conf] [4] dosyasını yerleştirin:
1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<openejb>
  <Resource id="Default JDBC Database">
    JdbcDriver com.mysql.jdbc.Driver
    JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
    UserName root
  </Resource>
</openejb>
  • [src / main/ resources/ META-INF] [5] klasörünü oluşturun
  • içine aşağıdaki [persistence.xml] ve [6] dosyalarını ekleyin:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence              http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
    <!-- JPA sağlayıcısı EclipseLink'tir -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <!-- Jpa varlıkları -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <!-- EclipseLink sağlayıcısının özellikleri -->
    <properties>
      <property name="eclipselink.logging.level" value="FINE"/>
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • 12. satır: EclipseLink'ten ayrıntılı günlükler istenir,
  • 13. satır: tablolar, JPA katmanının oluşturulmasıyla birlikte oluşturulacaktır,
  • Projenin [pom.xml] dosyasına OpenEJB ve EclipseLink kütüphanelerini ve MySQL sürücüsünü ekleyin:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>istia.st</groupId>
  <artifactId>mv-pam-openejb-eclipselink</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-pam-openejb-eclipselink</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.openejb</groupId>
      <artifactId>openejb-core</artifactId>
      <version>4.0.0</version>
    </dependency>                
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>eclipselink</artifactId>
      <version>2.3.0</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>javax.persistence</artifactId>
      <version>2.0.3</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>org.swinglabs</groupId>
      <artifactId>swing-layout</artifactId>
      <version>1.0.3</version>
    </dependency>
  </dependencies>
  
  <repositories>
    <repository>
      <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
      <id>eclipselink</id>
      <layout>default</layout>
      <name>Repository for library Library[eclipselink]</name>
    </repository>
  </repositories>
  
</project>
  • 18-22. satırlar: OpenEJB bağımlılığı,
  • 30-39. satırlar: EclipseLink bağımlılıkları,
  • 41-44. satırlar: MySQL'in JDBC sürücüsüne olan bağımlılığı

6.2.3. [DAO] katmanının taşınması

[DAO] katmanını, [mv-pam-spring-hibernate] projesindeki paketleri [mv-pam-openejb-eclipselink] projesine kopyalayarak taşıyacağız.

  • [dao, exception, jpa] paketlerini kopyalama
 

Yukarıda bildirilen hatalar, kopyalanan [DAO] katmanının Spring kullandığı ve Spring kütüphanelerinin artık projenin bir parçası olmadığı gerçeğinden kaynaklanmaktadır.

6.2.3.1. EJB [CotisationDao]

Gelecekteki EJB ve [CotisationDao] için yerel ve uzak arayüzleri oluşturuyoruz:

Yerel arayüz ICotisationDaoLocal:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Local;

@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}

Doğru paketleri elde etmek için import, [clic droit sur le code / Fix Imports] komutunu çalıştırın.

Uzak arayüz ICotisationDaoRemote:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Remote;

@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}

Ardından [CotisationDao] sınıfını değiştirerek EJB haline getiriyoruz:

...
import javax.persistence.PersistenceContext;
import jpa.Cotisation;

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {

  @PersistenceContext
  private EntityManager em;
...  

Bu sınıfın Spring çerçevesine eklediği import ortadan kalkar. Projeden bir [Clean and Build] oluşturun:

[1]'te, [CotisationDao] sınıfında artık hata yok.

6.2.3.2. EJB, [EmployeDao] ve [IndemniteDao]

Aynı işlem, [DAO] katmanındaki diğer öğeler için de tekrarlanır:

  • IEmployeDao'ten türetilen IEmployeDaoLocal ve IEmployeDaoRemote arayüzleri
  • bu iki arayüzü uygulayan EJB ve EmployeDao
  • IIndemniteDao'ten türetilen IIndemniteDaoLocal ve IIndemniteDaoRemote arayüzleri
  • EJB ve IndemniteDao, bu iki arayüzü uyguluyor

Bu işlem tamamlandıktan sonra, [2] projesinde artık hata kalmamıştır.

6.2.3.3. [PamException] sınıfı

[PamException] sınıfı, tek bir ayrıntı dışında eskisi gibi kalır:

package exception;

import javax.ejb.ApplicationException;

@ApplicationException(rollback=true)
public class PamException extends RuntimeException {

   // hata kodu
  private int code;
...
  1. satır eklenmiştir. Doğru import değerlerini elde etmek için [Fix imports] yapın.

  2. satırdaki açıklamayı anlamak için, [DAO] katmanımızdaki her bir EJB yönteminin:

  • EJB konteyneri tarafından başlatılan ve sonlandırılan bir işlem içinde yürütülür
  • bir sorun oluştuğunda [PamException] türünde bir istisna atar

[metier] katmanı, [DAO] katmanındaki bir M yöntemini çağırdığında, bu çağrı EJB konteyneri tarafından yakalanır. Her şey, [metier] katmanı ile [DAO] katmanı arasında, burada [Proxy EJB] olarak adlandırılan ve [DAO] katmanına yapılan tüm çağrıları yakalayan bir ara sınıf varmış gibi gerçekleşir. [DAO] katmanındaki M yöntemine yapılan çağrı yakalandığında, EJB Proxy'si bir işlem başlatır ve ardından kontrolü [DAO] katmanındaki M yöntemine devreder; bu yöntem de bu işlem içinde yürütülür. M yöntemi, istisna ile veya istisnasız olarak sona erer.

  • M yöntemi istisna olmadan sona ererse, yürütme EJB proxy'sine geri döner ve bu proxy, commit ile işlemi onaylayarak sonlandırır. Yürütme akışı daha sonra [metier] katmanındaki çağıran yönteme geri verilir
  • M yöntemi bir istisna ile sona ererse, yürütme EJB proxy'sine geri döner; bu proxy, rollback ile işlemi geçersiz kılıp sonlandırır. Ayrıca bu istisnayı bir EJBException türüne kapsüller. Yürütme akışı daha sonra [metier] katmanındaki çağıran yönteme geri verilir; bu yöntem de bir EJBException alır. Yukarıdaki 5. satırdaki açıklama, bu kapsüllemeyi engeller. Dolayısıyla [metier] katmanı bir PamException alacaktır. Ayrıca, rollback=true özniteliği, EJB proxy'sine, bir PamException aldığında işlemi geçersiz kılmasını belirtir.

6.2.3.4. [DAO] katmanının testi

EJB tarafından uygulanan [DAO] katmanımız test edilebilir. İlk olarak, [mv-pam-springhibernate] projesindeki [Test Packages] paketini, geliştirilmekte olan [1] projesine kopyalıyoruz:

Veritabanını birkaç [2] verisiyle başlatan [JUnitInitDB] testini saklıyoruz. [ JUnitInitDbLocal] sınıfını [3] olarak yeniden adlandırıyoruz. [JUnitInitDBLocal] sınıfı, [DAO] katmanındaki EJB sınıfının yerel arayüzünü kullanacaktır.

Öncelikle [JUnitInitDBLocal] sınıfını şu şekilde değiştiriyoruz:

public class JUnitInitDBLocal {

  static private IEmployeDaoLocal employeDao = null;
  static private ICotisationDaoLocal cotisationDao = null;
  static private IIndemniteDaoLocal indemniteDao = null;

  @BeforeClass
  public static void init() throws Exception {
     // gömülü Open EJB konteyneri yapılandırılıyor
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // JNDI bağlamının önceki özelliklerle başlatılması
    InitialContext initialContext = new InitialContext(properties);
     // yerel DAO katmanlarının örneklenmesi
    employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
}

...
  • 3-5. satırlar: [DAO] katmanındaki EJB'in yerel arayüzlerine yapılan referanslar
  • 7. satır: @BeforeClass, JUnit testinin başlatılması sırasında yürütülen yöntemi belirtir
  • satır 10-13: OpenEJB konteynerinin başlatılması. Bu başlatma işlemine özgüdür ve her EJB konteyneri için farklılık gösterir.
  • 13. satır: JNDI (Java Naming and Directory Interface) bağlamı mevcuttur; bu bağlam, EJB'lere adlar aracılığıyla erişilmesini sağlar. OpenEJB ile, bir EJB E'nin yerel arayüzü ELocal, uzak arayüzü ise ERemote olarak belirtilir.
  • 15-17. satırlar: JNDI bağlamından, EJB ve [EmployeDao, CotisationDao, IndemniteDao]'in yerel arayüzlerine ilişkin bir referans istenir.

Proje derlenir (Build), gerekirse MySQL sunucusu başlatılır ve JUnitInitDBLocal testi çalıştırılır. [persistence.xml] dosyasının her çalıştırmada tabloları yeniden oluşturacak şekilde yapılandırıldığını hatırlatırız. Testi çalıştırmadan önce, MySQL ve [dbpam_eclipselink] veritabanlarındaki olası tabloları silmeniz tavsiye edilir.

  • [1]'te, [Services] sekmesinde, 6.2.1. paragrafında kurulan NetBeans bağlantısına ait tablolar silinir.
  • [2]'te, [dbpam_eclipselink] veritabanında artık tablo kalmamıştır
  • [3]'te proje derlenir
  • [4]'te, JUnitInitDBLocal testi çalıştırılır
  • [5]'te test başarıyla tamamlandı
  • [6]'te, NetBeans bağlantısı yenileniyor
  • [7]'te, JPA katmanı tarafından oluşturulan 4 tablo görülüyor. Testin amacı bu tabloları doldurmaktı. Bunlardan birinin içeriği görüntüleniyor
  • [8]'te, [EMPLOYES] tablosunun içeriği

OpenEJB konteyneri konsolda şu günlükleri görüntüledi:

Infos - PersistenceUnit(name=dbpam_eclipselinkPU, provider=org.eclipse.persistence.jpa.PersistenceProvider) - provider time 396ms
Infos - Jndi(name=CotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=CotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=EmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=EmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=IndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=IndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao) --> Ejb(deployment-id=IndemniteDao)
Infos - existing thread singleton service in SystemInstance() org.apache.openejb.cdi.ThreadSingletonServiceImpl@624a240d
Infos - OpenWebBeans Container is starting...
Infos - Adding OpenWebBeansPlugin : [CdiPlugin]
Infos - All injection points were validated successfully.
Infos - OpenWebBeans Container has started, it took [70] ms.
Infos - Created Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Deployed Application(path=D:\data\istia-1112\netbeans\glassfish\mv-pam\tmp\mv-pam-openejb-eclipselink\classpath.ear)
  • 2-3. satırlar: EJB ve [CotisationDaoLocal]'in her ikisinin de adı JNDI,
  • 4-5. satırlar: EJB ve [CotisationDaoRemote]'e ait iki ad, JNDI,
  • 7-8. satırlar: EJB ve [EmployeDaoLocal]'ten gelen iki isim JNDI,
  • 9-10. satırlar: EJB ve [EmployeDaoRemote]'ten gelen iki isim JNDI,
  • 12-13. satırlar: EJB ve [IndemniteDaoLocal]'ten oluşan JNDI adlı iki isim,
  • satır 14-15: EJB ve [EmployeDaoRemote]'in her ikisi de JNDI olarak.

Aynı testi, bu sefer EJB'in uzaktan arayüzünü kullanarak tekrar yapıyoruz.

[1]'te, [JUnitInitDBLocal] sınıfı [JUnitInitDBRemote]'e kopyalanmıştır (kopyala/yapıştır). Bu sınıfta, yerel arayüzleri uzak arayüzlerle değiştiriyoruz:

public class JUnitInitDBRemote {

  static private IEmployeDaoRemote employeDao = null;
  static private ICotisationDaoRemote cotisationDao = null;
  static private IIndemniteDaoRemote indemniteDao = null;

  @BeforeClass
  public static void init() throws Exception {
     // gömülü Open EJB konteyneri yapılandırılıyor
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // JNDI bağlamının önceki özelliklerle başlatılması
    InitialContext initialContext = new InitialContext(properties);
     // DAO uzak katmanlarının örneklenmesi
    employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
}

Bu işlem tamamlandıktan sonra, yeni test sınıfı çalıştırılabilir. Öncesinde, NetBeans'teki [dbpam_eclipselink] bağlantısı ile [dbpam_eclipselink] veritabanındaki tabloları silin.

 

NetBeans [dbpam_eclipselink] bağlantısı ile veritabanının doldurulduğunu kontrol edin.

6.2.4. [metier] katmanının taşınması

[metier] katmanını, [mv-pam-spring-hibernate] projesindeki paketleri [mv-pam-openejb-eclipselink] projesine kopyalayarak taşıyacağız.

Yukarıda bildirilen [1] hataları, kopyalanan [metier] katmanının Spring kullandığı ve Spring kütüphanelerinin artık projenin bir parçası olmadığı gerçeğinden kaynaklanmaktadır.

6.2.4.1. EJB [Metier]

EJB ve [CotisationDao] için açıklanan adımların aynısını izliyoruz. Öncelikle [2]'te, gelecekteki EJB ve [Metier] için yerel ve uzak arayüzleri oluşturuyoruz. Her ikisi de ilk arayüz olan [IMetier]'ten türetilmiştir.

1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{

}
1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{

}

Bunu yaptıktan sonra, [3]'te [Metier] sınıfını, EJB olacak şekilde değiştiriyoruz:

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {

   // yerel [DAO] katmanına referanslar
  @EJB
  private ICotisationDaoLocal cotisationDao = null;
  @EJB
  private IEmployeDaoLocal employeDao = null;
  @EJB
  private IIndemniteDaoLocal indemniteDao = null;

   // maaş bordrosunu alma
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
          double nbHeuresTravaillées, int nbJoursTravaillés) {
     // çalışanla ilgili bilgileri alıyoruz
...
  • 1. satır: @Stateless anotasyonu, sınıfı bir EJB haline getirir
  • 2. satır: Sınıfın her yöntemi bir işlem içinde çalışacaktır
  • 3. satır: EJB [Metier], az önce tanımladığımız hem yerel hem de uzak arayüzleri uygular
  • 7. satır: EJB [Metier], EJB [CotisationDao]'i, onun yerel arayüzü aracılığıyla kullanacaktır. Bu, [metier] ve [DAO] katmanlarının aynı JVM içinde çalışması gerektiği anlamına gelir.
  • 6. satır: @EJB anotasyonu, EJB konteynerinin, EJB ve [CotisationDao]’in yerel arayüzüne referansı kendisi enjekte etmesini sağlar. Karşılaştığımız diğer yöntem ise bir JNDI bağlamı kullanmaktır.
  • 8-11. satırlar: [DAO] katmanındaki diğer iki EJB için de aynı mekanizma kullanılır.

6.2.4.2. [metier] katmanının testi

Bir EJB tarafından uygulanan [metier] katmanımız test edilebilir. İlk olarak, [mv-pam-spring-hibernate] projesindeki [Test Packages] katmanından [metier] paketini, geliştirilmekte olan [1] projesine kopyalıyoruz:

  • [1]'e, kopyalama işlemi sonucunda
  • [2]'e, ilk test silinir
  • [3]'te, kalan testin adı [JUnitMetierLocal] olarak değiştirilir

[JUnitMetierLocal] sınıfı şu şekilde olur:

public class JUnitMetierLocal {

// yerel iş katmanı
  static private IMetierLocal metier;

  @BeforeClass
  public static void init() throws NamingException {
     // gömülü Open EJB konteynerini yapılandırma
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // JNDI bağlamını önceki özelliklerle başlatma
    InitialContext initialContext = new InitialContext(properties);

     // yerel katmanların DAO örneklenmesi
    IEmployeDaoLocal employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    ICotisationDaoLocal cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    IIndemniteDaoLocal indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
     // yerel iş katmanının örneklenmesi
    metier = (IMetierLocal) initialContext.lookup("MetierLocal");

     // veritabanı boşaltılıyor
...
}
  • 4. satır: EJB'in yerel arayüzüne bir referans [Metier]
  • 8-12. satırlar: OpenEJB konteynerinin yapılandırması, [DAO] katmanındaki testte yapılanla aynıdır
  • satır 15-19: 12. satırdaki JNDI bağlamından, [DAO] katmanındaki 3 adet EJB ve [metier] katmanındaki EJB referansları istenmektedir. [DAO] katmanındaki EJB'ler veritabanını başlatmak için, [metier] katmanındaki EJB ise maaş hesaplama testleri yapmak için kullanılacaktır.

[JUnitMetierLocal] testinin çalıştırılması sonucunda şu sonuç elde edilir: [1]:

[2]'te, bu sefer EJB ve [Metier]'in uzaktan arayüzünü test etmek için [JUnitMetierLocal]'i [JUnitMetierRemote] olarak kopyalıyoruz. [JUnitMetierRemote] kodunda bu uzaktan arayüzü kullanacak şekilde değişiklik yapılır. Geri kalan kısımda herhangi bir değişiklik yapılmaz.

public class JUnitMetierRemote {

   // uzak iş katmanı
  static private IMetierRemote metier;

  @BeforeClass
  public static void init() throws NamingException {
     // gömülü Open EJB konteyneri yapılandırılıyor
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // JNDI bağlamının önceki özelliklerle başlatılması
    InitialContext initialContext = new InitialContext(properties);

     // DAO uzak katmanlarının örneklenmesi
    IEmployeDaoRemote employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    ICotisationDaoRemote cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    IIndemniteDaoRemote indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
     // uzak iş katmanının örneklenmesi
    metier = (IMetierRemote) initialContext.lookup("MetierRemote");

     // veritabanı boşaltılıyor
    for(Employe employe:employeDao.findAll()){
      employeDao.destroy(employe);
    }
    for(Cotisation cotisation:cotisationDao.findAll()){
      cotisationDao.destroy(cotisation);
    }
    for(Indemnite indemnite : indemniteDao.findAll()){
      indemniteDao.destroy(indemnite);
    }
     // veritabanını doldurma
    Indemnite indemnite1=new Indemnite(1,1.93,2,3,12);
    Indemnite indemnite2=new Indemnite(2,2.1,2.1,3.1,15);
    indemnite1=indemniteDao.create(indemnite1);
    indemnite2=indemniteDao.create(indemnite2);
    employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",indemnite2));
    employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",indemnite1));
    cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
  }
}
  • 4. ve 19. satırlar: EJB ve [Metier]'in uzaktan arayüzü kullanılıyor.
  • 15-17. satırlar: [DAO] katmanının uzaktan arayüzleri kullanılır
  • 34-35. satırlar: Uzak arayüzlerde, istemci ile sunucu arasında nesneler değer olarak aktarıldığından, create(Indemnite i) yöntemi tarafından döndürülen sonucu almak gerekir. Nesnelerin referans olarak aktarıldığı yerel arayüzlerde bu zorunlu değildi.

Bu işlem tamamlandıktan sonra proje derlenebilir ve [JUnitMetierRemote] testi çalıştırılabilir:

  

6.2.5. [console] katmanının taşınması

[console] katmanını, [mv-pam-spring-hibernate] projesindeki paketleri [mv-pam-openejb-eclipselink] projesine kopyalayarak taşıyacağız.

Yukarıda bildirilen [1] hataları, kopyalanan [metier] katmanının Spring kullandığı ve Spring kütüphanelerinin artık projenin bir parçası olmadığı gerçeğinden kaynaklanmaktadır. [2]'te, [Main] sınıfı [MainLocal] olarak yeniden adlandırılmıştır. Bu sınıf, EJB'in yerel arayüzünü kullanacaktır.

[MainLocal] sınıfının kodu şu şekilde değişir:

  public static void main(String[] args) {
     // yerel veriler
    final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
...
     // hata mı var?
    if (erreurs.size() != 0) {
      for (int i = 0; i < erreurs.size(); i++) {
        System.err.println(erreurs.get(i));
      }
      return;
    }
     // tamam - [métier] katmanından maaş bordrosu istenebilir
    IMetierLocal metier = null;
    FeuilleSalaire feuilleSalaire = null;
    try {
       // gömülü Open EJB konteynerini yapılandırıyoruz
      Properties properties = new Properties();
      properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
       // JNDI bağlamını önceki özelliklerle başlatıyoruz
      InitialContext initialContext = new InitialContext(properties);
       // yerel iş katmanının örneklenmesi
      metier = (IMetierLocal) initialContext.lookup("MetierLocal");
       // maaş bordrosu hesaplanıyor
      feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
    } catch (PamException ex) {
      System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
      return;
    } catch (Exception ex) {
      System.err.println("L'erreur suivante s'est produite : " + ex.toString());
      return;
    }
     // ayrıntılı görüntüleme
    String output = "Valeurs saisies :\n";
    output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
....

Değişiklikler 13-25. satırlarda yer almaktadır. [metier] katmanına yapılan referansın değiştirilme şekli budur (17-22. satırlar). Önceki örneklerde zaten görülen yeni kodu burada açıklamayacağız. Bu değişiklikler yapıldıktan sonra, projede artık hata kalmaz (bkz. [3]).

Projeyi, [1] argümanlarıyla çalıştırılacak şekilde yapılandırıyoruz:

Konsol uygulamasının normal şekilde çalışması için veritabanında veri bulunması gerekir. Bunun için [META-INF/persistence.xml] dosyasını değiştirmemiz gerekiyor:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence              http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
    <!-- JPA sağlayıcısı EclipseLink'tir -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <!-- Jpa varlıkları -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <!-- EclipseLink sağlayıcısının özellikleri -->
    <properties>
      <property name="eclipselink.logging.level" value="FINE"/>
      <!--
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
      -->
    </properties>
  </persistence-unit>
</persistence>

Her çalıştırmada veritabanı tablolarının yeniden oluşturulmasına neden olan 14. satır yorum satırına alınır. Bu değişikliğin geçerli olması için projenin yeniden derlenmesi (Clean and Build) gerekir. Bu işlem tamamlandıktan sonra program çalıştırılabilir. Her şey yolunda giderse, aşağıdaki gibi bir konsol çıktısı elde edilir:

.......
INFO - Created EJB(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
INFO - Deployed Application(path=classpath.ear)
[EL Info]: 2009-09-30 15:09:21.109--ServerSession(16658781)--EclipseLink, version: Eclipse Persistence Services - 1.1.2.v20090612-r4475
[EL Info]: 2009-09-30 15:09:21.937--ServerSession(16658781)--file:/C:/temp/09-09-28/pam-console-metier-dao-openejb-eclipselink-0910/build/classes/-jpa login successful
Valeurs saisies :
N° de sécurité sociale de l'employé : 254104940426058
Nombre d'heures travaillées : 150
Nombre de jours travaillés : 20

Informations Employé : 
Nom : Jouveinal
Prénom : Marie
Adresse : 5 rue des oiseaux
Ville : St Corentin
Code Postal : 49203
Indice : 2

Informations Cotisations : 
CSGRDS : 3.49 %
CSGD : 6.15 %
Retraite : 7.88 %
Sécurité sociale : 9.39 %

Informations Indemnités : 
Salaire horaire : 2.1 euro
Entretien/jour : 2.1 euro
Repas/jour : 3.1 euro
Congés Payés : 15.0 %

Informations Salaire : 
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'entretien : 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro

BUILD SUCCESSFUL (total time: 4 seconds)

Burada [metier] katmanının yerel arayüzünü kullandık. Şimdi ikinci bir konsol sınıfında bu katmanın uzaktan arayüzünü kullanıyoruz:

[1]'te, [MainLocal] sınıfı [MainRemote] olarak kopyalanmıştır. [MainRemote] kodunda, [metier] katmanının uzaktan arayüzünü kullanmak üzere değişiklik yapıldı:

// tamam - [metier] katmanından maaş bordrosunu talep edebiliriz
    IMetierRemote metier = null;
    FeuilleSalaire feuilleSalaire = null;
    try {
       // gömülü Open EJB konteynerini yapılandırıyoruz
...
       // uzak iş katmanının örneklenmesi
      metier = (IMetierRemote) initialContext.lookup("MetierRemote");
       // maaş bordrosu hesaplanıyor
      feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
    } catch (PamException ex) {
...
    } catch (Exception ex) {
...
    }

Değişiklikler 2. ve 8. satırlarda yapılmıştır. Proje, [2] olarak yapılandırılarak [MainRemote] sınıfını çalıştıracak şekilde ayarlanmıştır. Çalıştırıldığında, önceki ile aynı sonuçlar elde edilir.

6.3. Conclusion

Spring / Hibernate mimarisini OpenEJB / EclipseLink mimarisine nasıl taşıyacağımızı gösterdik.

Spring / Hibernate mimarisi

OpenEJB / EclipseLink mimarisi

Uygulama, başlangıçta katmanlar halinde yapılandırılmış olduğu için taşıma işlemi fazla zorluk çekilmeden gerçekleştirilebildi. Bu noktayı anlamak önemlidir.