Çok Kiracılı Bir Uygulama Nasıl Oluşturulur: Hazırda Bekletme Eğitimi

Yayınlanan: 2022-03-11

Her müşterinin kendi ayrı verisine sahip olduğu bulut uygulamalarından bahsettiğimizde, bu verileri nasıl depolayıp manipüle edeceğimizi düşünmemiz gerekiyor. Dışarıdaki tüm harika NoSQL çözümlerine rağmen, bazen hala eski güzel ilişkisel veritabanını kullanmamız gerekiyor. Verileri ayırmak için akla gelebilecek ilk çözüm, her tabloya ayrı ayrı ele alınabilmesi için bir tanımlayıcı eklemektir. Bu işe yarar, ancak bir müşteri veritabanını isterse ne olur? Diğerleri arasında gizlenmiş tüm bu kayıtları almak çok zahmetli olurdu.

Hibernate ile Çoklu Kiralama Java EE Uygulaması

Java'da çoklu kullanım, Hibernate ile her zamankinden daha kolay.
Cıvıldamak

Hibernate ekibi bir süre önce bu soruna bir çözüm buldu. Verilerin nereden alınacağını kontrol etmeyi sağlayan bazı uzatma noktaları sağlarlar. Bu çözüm, verileri bir tanımlayıcı sütun, birden çok veritabanı ve birden çok şema aracılığıyla kontrol etme seçeneğine sahiptir. Bu makale çoklu şema çözümünü kapsayacaktır.

Öyleyse işe koyulalım!

Başlarken

Daha deneyimli bir Java geliştiricisiyseniz ve her şeyi nasıl yapılandıracağınızı biliyorsanız veya zaten kendi Java EE projeniz varsa, bu bölümü atlayabilirsiniz.

Öncelikle yeni bir Java projesi oluşturmamız gerekiyor. Eclipse ve Gradle kullanıyorum, ancak tercih ettiğiniz IDE ve IntelliJ ve Maven gibi oluşturma araçlarını kullanabilirsiniz.

Benimle aynı araçları kullanmak istiyorsanız, projenizi oluşturmak için şu adımları takip edebilirsiniz:

  • Eclipse'e Gradle eklentisini kurun
  • Dosya -> Yeni -> Diğer… seçeneğine tıklayın.
  • Gradle'ı (STS) bulun ve İleri'ye tıklayın
  • Bir ad verin ve örnek proje için Java Quickstart'ı seçin
  • Bitir'i tıklayın

Harika! Bu, ilk dosya yapısı olmalıdır:

 javaee-mt |- src/main/java |- src/main/resources |- src/test/java |- src/test/resources |- JRE System Library |- Gradle Dependencies |- build |- src |- build.gradle

Sadece örnek dosyalar oldukları için kaynak klasörlerin içindeki tüm dosyaları silebilirsiniz.

Projeyi yürütmek için Wildfly kullanıyorum ve nasıl yapılandırılacağını göstereceğim (yine favori aracınızı burada kullanabilirsiniz):

  • Wildfly'ı indirin: http://wildfly.org/downloads/ (Sürüm 10 kullanıyorum)
  • Dosyayı açın
  • JBoss Tools eklentisini Eclipse'e yükleyin
  • Sunucular sekmesinde, herhangi bir boş alana sağ tıklayın ve Yeni -> Sunucu'yu seçin.
  • Wildfly 10.x'i seçin (9.x, Eclipse sürümünüze bağlı olarak 10 yoksa da çalışır)
  • İleri'ye tıklayın, Yeni Çalışma Zamanı Oluştur'u seçin (sonraki sayfa) ve tekrar İleri'ye tıklayın.
  • Wildfly'ı açtığınız klasörü Ana Dizin olarak seçin
  • Bitir'i tıklayın

Şimdi Wildfly'ı veritabanını tanıyacak şekilde yapılandıralım:

  • Wildfly klasörünüzün içindeki bin klasörüne gidin
  • add-user.bat veya add-user.sh yürütün (işletim sisteminize bağlı olarak)
  • Kullanıcınızı Yönetici olarak oluşturmak için adımları izleyin
  • Eclipse'de tekrar Sunucular sekmesine gidin, oluşturduğunuz sunucuya sağ tıklayın ve Başlat'ı seçin.
  • Tarayıcınızda, Yönetim Arayüzü olan http://localhost:9990'a erişin
  • Yeni oluşturduğunuz kullanıcının kimlik bilgilerini girin
  • Veritabanınızın sürücü kavanozunu dağıtın:
    1. Dağıtım sekmesine gidin ve Ekle'yi tıklayın.
    2. İleri'ye tıklayın, sürücü jar dosyanızı seçin
    3. İleri ve Bitir'e tıklayın
  • Yapılandırma sekmesine gidin
  • Alt Sistemler -> Veri Kaynakları -> XA Dışı'nı seçin
  • Ekle'ye tıklayın, veritabanınızı seçin ve İleri'ye tıklayın.
  • Veri kaynağınıza bir ad verin ve İleri'ye tıklayın.
  • Sürücü Algıla sekmesini seçin ve az önce dağıttığınız sürücüyü seçin
  • Veritabanı bilgilerinizi girin ve İleri'ye tıklayın
  • Önceki adımdaki bilgilerin doğru olduğundan emin olmak istiyorsanız Bağlantıyı Test Et'e tıklayın.
  • Bitir'i tıklayın
  • Eclipse'e geri dönün ve çalışan sunucuyu durdurun
  • Üzerine sağ tıklayın, Ekle ve Kaldır'ı seçin
  • Projenizi sağa ekleyin
  • Bitir'i tıklayın

Pekala, Eclipse ve Wildfly'ı birlikte yapılandırdık!

Bu, projenin dışında gerekli olan tüm konfigürasyonlardır. Proje konfigürasyonuna geçelim.

Önyükleme Projesi

Artık Eclipse ve Wildfly'ı yapılandırdığımıza ve projemizi oluşturduğumuza göre, projemizi yapılandırmamız gerekiyor.

Yapacağımız ilk şey build.gradle dosyasını düzenlemek. Bu nasıl görünmesi gerektiği:

 apply plugin: 'java' apply plugin: 'war' apply plugin: 'eclipse' apply plugin: 'eclipse-wtp' sourceCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' compileJava.options.encoding = 'UTF-8' compileTestJava.options.encoding = 'UTF-8' repositories { jcenter() } eclipse { wtp { } } dependencies { providedCompile 'org.hibernate:hibernate-entitymanager:5.0.7.Final' providedCompile 'org.jboss.resteasy:resteasy-jaxrs:3.0.14.Final' providedCompile 'javax:javaee-api:7.0' }

Bağımlılıkların tümü “providedCompile” olarak bildirilir, çünkü bu komut bağımlılığı son savaş dosyasına eklemez. Wildfly zaten bu bağımlılıklara sahiptir ve aksi takdirde uygulamanınkilerle çakışmalara neden olur.

Bu noktada projenize sağ tıklayabilir, az önce bildirdiğimiz bağımlılıkları içe aktarmak için Gradle (STS) -> Refresh All öğesini seçebilirsiniz.

Hazırda Bekletme'nin ihtiyaç duyduğu bilgileri içeren dosya olan "persistence.xml" dosyasını oluşturma ve yapılandırma zamanı:

  • src/main/resource kaynak klasöründe META-INF adlı bir klasör oluşturun.
  • Bu klasörün içinde persistence.xml adlı bir dosya oluşturun.

Dosyanın içeriği aşağıdaki gibi olmalıdır, jta-data-source'u Wildfly'da oluşturduğunuz veri kaynağıyla ve com.toptal.andrehil.mt.hibernate package com.toptal.andrehil.mt.hibernate bir sonraki adımda oluşturacağınız veri kaynağıyla eşleştirmek için değiştirin. bölüm (aynı paket adını seçmediğiniz sürece):

 <?xml version="1.0" encoding="UTF-8" ?> <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" version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"> <persistence-unit name="pu"> <jta-data-source>java:/JavaEEMTDS</jta-data-source> <properties> <property name="hibernate.multiTenancy" value="SCHEMA"/> <property name="hibernate.tenant_identifier_resolver" value="com.toptal.andrehil.mt.hibernate.SchemaResolver"/> <property name="hibernate.multi_tenant_connection_provider" value="com.toptal.andrehil.mt.hibernate.MultiTenantProvider"/> </properties> </persistence-unit> </persistence>

Hazırda Bekletme Sınıfları

persistence.xml dosyasına eklenen konfigürasyonlar, MultiTenantProvider ve SchemaResolver olmak üzere iki özel sınıfa işaret eder. Birinci sınıf, doğru şema ile yapılandırılmış bağlantıları sağlamaktan sorumludur. İkinci sınıf, kullanılacak şemanın adının çözümlenmesinden sorumludur.

İşte iki sınıfın uygulanması:

 public class MultiTenantProvider implements MultiTenantConnectionProvider, ServiceRegistryAwareService { private static final long serialVersionUID = 1L; private DataSource dataSource; @Override public boolean supportsAggressiveRelease() { return false; } @Override public void injectServices(ServiceRegistryImplementor serviceRegistry) { try { final Context init = new InitialContext(); dataSource = (DataSource) init.lookup("java:/JavaEEMTDS"); // Change to your datasource name } catch (final NamingException e) { throw new RuntimeException(e); } } @SuppressWarnings("rawtypes") @Override public boolean isUnwrappableAs(Class clazz) { return false; } @Override public <T> T unwrap(Class<T> clazz) { return null; } @Override public Connection getAnyConnection() throws SQLException { final Connection connection = dataSource.getConnection(); return connection; } @Override public Connection getConnection(String tenantIdentifier) throws SQLException { final Connection connection = getAnyConnection(); try { connection.createStatement().execute("SET SCHEMA '" + tenantIdentifier + "'"); } catch (final SQLException e) { throw new HibernateException("Error trying to alter schema [" + tenantIdentifier + "]", e); } return connection; } @Override public void releaseAnyConnection(Connection connection) throws SQLException { try { connection.createStatement().execute("SET SCHEMA 'public'"); } catch (final SQLException e) { throw new HibernateException("Error trying to alter schema [public]", e); } connection.close(); } @Override public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException { releaseAnyConnection(connection); } }

Yukarıdaki ifadelerde kullanılan sözdizimi PostgreSQL ve diğer bazı veritabanları ile çalışır, mevcut şemayı değiştirmek için veritabanınızın farklı bir sözdizimine sahip olması durumunda bunun değiştirilmesi gerekir.

 public class SchemaResolver implements CurrentTenantIdentifierResolver { private String tenantIdentifier = "public"; @Override public String resolveCurrentTenantIdentifier() { return tenantIdentifier; } @Override public boolean validateExistingCurrentSessions() { return false; } public void setTenantIdentifier(String tenantIdentifier) { this.tenantIdentifier = tenantIdentifier; } }

Bu noktada, uygulamayı test etmek zaten mümkün. Şimdilik, çözümleyicimiz doğrudan sabit kodlanmış bir genel şemaya işaret ediyor, ancak zaten çağrılıyor. Bunun için sunucunuz çalışıyorsa durdurup yeniden başlatın. Hata ayıklama modunda çalıştırmayı deneyebilir ve çalışıp çalışmadığını kontrol etmek için yukarıdaki sınıfların herhangi bir noktasına kesme noktası yerleştirebilirsiniz.

Çözümleyicinin Pratik Kullanımı

Öyleyse, çözümleyici şemanın doğru adını nasıl içerebilir?

Bunu başarmanın bir yolu, tüm isteklerin başlığında bir tanımlayıcı tutmak ve ardından şema adını enjekte etmek için bir filtre oluşturmaktır.

Kullanımı örneklemek için bir filtre sınıfı uygulayalım. Çözümleyiciye Hibernate'in SessionFactory aracılığıyla erişilebilir, bu nedenle onu almak ve doğru şema adını enjekte etmek için bundan faydalanacağız.

 @Provider public class AuthRequestFilter implements ContainerRequestFilter { @PersistenceUnit(unitName = "pu") private EntityManagerFactory entityManagerFactory; @Override public void filter(ContainerRequestContext containerRequestContext) throws IOException { final SessionFactoryImplementor sessionFactory = ((EntityManagerFactoryImpl) entityManagerFactory).getSessionFactory(); final SchemaResolver schemaResolver = (SchemaResolver) sessionFactory.getCurrentTenantIdentifierResolver(); final String username = containerRequestContext.getHeaderString("username"); schemaResolver.setTenantIdentifier(username); } }

Şimdi, herhangi bir sınıf veritabanına erişmek için bir EntityManager aldığında, zaten doğru şema ile yapılandırılacaktır.

Basitlik adına, burada gösterilen uygulama, tanımlayıcıyı doğrudan başlıktaki bir dizeden alıyor, ancak bir kimlik doğrulama belirteci kullanmak ve tanımlayıcıyı belirteçte depolamak iyi bir fikirdir. Bu konu hakkında daha fazla bilgi edinmekle ilgileniyorsanız, JSON Web Belirteçlerine (JWT) bir göz atmanızı öneririm. JWT, belirteç manipülasyonu için güzel ve basit bir kütüphanedir.

Bunların Hepsi Nasıl Kullanılır?

Her şey yapılandırıldığında, varlıklarınızda ve/veya EntityManager ile etkileşime giren sınıflarınızda yapmanız gereken başka bir şey yoktur. Bir EntityManager'dan çalıştırdığınız her şey, oluşturulan filtre tarafından çözülen şemaya yönlendirilecektir.

Şimdi yapmanız gereken tek şey, istemci tarafında istekleri engellemek ve sunucu tarafına gönderilmek üzere başlıktaki tanımlayıcıyı/belirteciyi enjekte etmektir.

Gerçek bir uygulamada, daha iyi bir kimlik doğrulama yöntemine sahip olacaksınız. Bununla birlikte, genel çoklu kiralama fikri aynı kalacaktır.

Makalenin sonundaki bağlantı, bu makaleyi yazmak için kullanılan projeye işaret etmektedir. 2 şema oluşturmak için Flyway'i kullanır ve projeyi test etmek için kullanılabilecek Car adlı bir varlık sınıfı ve CarService adlı bir dinlenme hizmeti sınıfı içerir. Aşağıdaki tüm adımları takip edebilirsiniz ancak kendi projenizi oluşturmak yerine onu klonlayıp bunu kullanabilirsiniz. Ardından, çalıştırırken basit bir HTTP istemcisi (Chrome için Postacı uzantısı gibi) kullanabilir ve anahtar:değer başlıklarıyla http://localhost:8080/javaee-mt/rest/cars için bir GET isteğinde bulunabilirsiniz:

  • kullanıcı adı:joe; veya
  • kullanıcı fred

Bunu yaparak, istekler, biri joe diğeri “fred” olarak adlandırılan farklı şemalarda bulunan farklı değerler döndürür.

Son sözler

Java dünyasında çok kullanıcılı uygulamalar oluşturmak için tek çözüm bu değildir, ancak bunu başarmanın basit bir yoludur.

Akılda tutulması gereken bir şey, Hazırda Bekletme'nin çoklu kiralama yapılandırmasını kullanırken DDL oluşturmamasıdır. Benim önerim, veritabanı oluşturmayı kontrol etmek için harika kütüphaneler olan Flyway veya Liquibase'e bir göz atmak. Hazırda Bekletme ekibi üretimde otomatik veritabanı oluşturmalarını kullanmamanızı tavsiye ettiğinden, çoklu kiracılık kullanmasanız bile bu yapılması güzel bir şeydir.

Bu makaleyi ve ortam yapılandırmasını oluşturmak için kullanılan kaynak kodu github.com/andrehil/JavaEEMT adresinde bulunabilir.

İlgili: Gelişmiş Java Sınıfı Eğitimi: Sınıf Yeniden Yükleme Kılavuzu