1.7. Data#
Uygulamalarda veri katmanında kullanılacak fonksiyonların sağlandığı altyapı bileşenidir. ORM işlemleri için JPA ve QueryDSL teknolojileri tercih edilip bu teknolojiler üzerinde özelleştirmeler yapılmıştır. Auditing altyapısı için Javers ve Envers teknolojilerine destek verilmektedir. Aynı zamanda sorgulama, view, auditing için özelleştirilmiş yetenekler sağlamaktadır ve ldap, redis gibi teknolojilerin veri katmanı destekleri de bu bileşende bulunmaktadır. Başlıca sağlanan katmanlar:
- Javers, Envers
- Model
- Entity
- JDBC İlklendirme
- PreLiquibase
- Model Çeviricileri
- Hibernate Anotasyonları
Converter#
Converter bileşeni Hibernate, JPA teknolojilerine bağlı olmadan nesneler arası dönüştürücüleri sağlamaktadır. Aynı zamanda enum sınıfların ordinal dışında veritabanı işlemleri için dönüştürücüsünü sağlamaktadır.
NOT:
HvlGenericConverter
veHvlGenericHibernateConverter
sınıfları runtime anında reflection ile çalıştığı için performansal olarak önerilmemektedir.HvlGenericHibernateConverter
dönüştürücüsü hibernate annotation'larına göre davranış göstermektedir. Herhangi bir veritabanı bağlılığı gerekmemektedir.
JPA#
Veritabanı işlemlerini sağlayan altyapı bileşenidir. Veritabanı işlemlerini yapacak olan sınıflar declarative olarak sağlanmaktadır.
JPA kullanırken transaction kullanımı önemlidir. Transaction için @Transactional
annotation'ını kullanılmaktadır. @Transactional
annotation'ını unchecked exception'larda direkt rollback almaktadır. Fakat checked işlemleri için geliştiricinin bu durumu ele alması beklenmektedir. Altyapıda bütün checked hatalar için rollback aktif olması için @HvlTransactionalRollbackForCheckedException
annotation'ı bulunmaktadır. Sorgulama işlemleri gibi read only
işlemler için ise @HvlTransactionalReadOnly
anotasyonu bulunmaktadır. Bu anotasyonlarda transaction'ı özelleştirebileceğiniz isolation, propogation gibi yetenekler de sağlanmaktadır.
Altyapıda veritabanı işlemlerinde kolon bazlı şifreleme yeteneği bulunmaktadır. @Convert
annotation'ı ile kullanılmaktadır.
import javax.persistence.Convert;
@Convert(converter = HvlStringCryptoConverter.class)
private String identityNumber;
NOT: Kolon şifreleme aktif etmek için 'application-database-datasource.yml' içerisinde
spring.jpa.properties.hibernate.encryption_enabled
alanınıntrue
yapılması gerekmektedir. Altyapı olarak şifreleme algoritması olarak şu anda AES desteklenmektedir. AES üzerinden şifreleme işleminde kullanılacak olankey
değerispring.jpa.properties.hibernate.encryption_key
alanından verilmesi gerekmektedir.
Altyapı kullanılarak yapılan veritabanı işlemlerinde işlemi kimin yaptığı, tarihi gibi işlem sırasında otomatik verilmesi gereken değerler HvlJpaAwareProvider
sınıfı ile yönetilmektedir. Geliştiriciler tarafından özelleştirilebilir.
JPA ile ilgili içeriğe ulaşmak için HvlJpaContext
veya HvlJpaContextHolder
sınıfı kullanılmalıdır.
Veritabanı işlemleri için altyapıdan PostgreSQL ve Oracle veritabanları için dialect bulunmaktadır. Bu dialect'ler özelleştirilmiştir. Örneğin; PostgreSQL için ilike vb. fonksiyonlar eklenmiştir.
NOT: Dialect, 'application-database-datasource.yml' içerisinde
spring.jpa.properties.hibernate.dialect
alanınından yönetilmektedir. Kullanılmak istenen dialect paket bilgisi ile verilmelidir. Postgresql içintr.com.havelsan.javarch.data.jpa.dialect.postgresql.HvlPostgreSQL10Dialect
ve Oracle içintr.com.havelsan.javarch.data.jpa.dialect.oracle.HvlOracleSQL12cDialect
altyapıdan sağlanan dialect'lerdir.
Altyapının sağladığı declarative repository sınıflarını kullanabilmek için konfigurasyon sınıfına aşağıdaki annotation eklenmelidir.
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import tr.com.havelsan.javarch.data.jpa.factory.HvlJpaRepositoryFactoryBean;
@EnableJpaRepositories(basePackages = {"base_package"},
repositoryFactoryBeanClass = HvlJpaRepositoryFactoryBean.class)
Veritabanı sorguları için Querydsl teknolojisi kullanılmaktadır. Altyapıya özgü alanların varsayılan değerlerinin verildiği ve sorgu (predicate) oluşturmaya yarayan HvlBaseQueryGenerator
sınıfından türeyen HvlEntityQueryGenerator
ve HvlViewQueryGenerator
sınıfı bulunmaktadır.
NOT: Sorgulama işlemi için bu sınıflardan türeyen sınıfları kullanmak önerilmektedir. 'Soft delete' gibi özel yapılar için yapılan değişiklikler desteklenmektedir. Geliştiricinin ekstra kontrol veya ekleme yapmasına gerek yoktur.
Veritabanı işlemleri için altyapı tarafından 4 tane arayüz sağlanmaktadır:
HvlJpaRepository
:HvlEntity
sınıfından türeyen sınıflar için kullanılan arayüzdür.HvlJpaSearchRepository
: Sorgulama ile ilgili fonksiyonların sağlandığı arayüzdür.HvlJpaSimpleRepository
:HvlSimpleEntity
sınıfından türeyen sınıflar için kullanılan arayüzdür.HvlJpaViewRepository
:HvlView
annotation'a sahip entity sınıfları için persist yeteneklerinin bulunmadığı arayüzdür.
Validasyon işlemleri için 2 adet anotasyon sağlanmaktadır:
@HvlIdValid
: Model ve entity içerisindeki obje alanlarda id alanı validasyonu yapmak için kullanılır.@HvlUuidValid
: Model ve entity içerisindeki obje alanlarda uuid alanı validasyonu yapmak için kullanılır.
hvl-infra altında bulunan 'application-database-datasource.yml, application-hvl-data.yml' dosyasıyla konfigure edilebilmektedir.
Dinamik Güncelleme (Dynamic Update)#
hvl-infra
projesinde application-database-datasource.yml
içerisindeki hvl.jpa.properties.hibernate.enable_dynamic_update
property'si varsayılan olarak true
değerini almaktadır. Bundan dolayı EKSEN altyapısı ile dynamic update yeteneği otomatik olarak devreye girecektir.
Eğer sınıfta 'dynamic update' çalışması istenmiyorsa @HvlDynamicUpdateExclude
annotation'ı entity üzerine eklenmelidir.
@Entity
@Table(name = TABLE_NAME)
@HvlDynamicUpdateExclude
public class HvlOAuthRole extends HvlSoftDeleteEntity {
}
NOT: 'dynamic update' performans için önemlidir. Varitabanı işlemlerinde 'update' işleminde sadece değişen alanların gönderilmesini sağlamaktadır.
Aşağıdaki kod bloğunun çalıştırıldığı varsayılırsa;
final HvlOAuthRole editableRole = roleRepository.getById(roleAssignerPersistModel.getRoleId());
editableRole.setName("Updated role name");
editableRole.setDescription("Updated role description");
roleRepository.updateWithoutFind(editableRole);
Dynamic update pasif durumda iken atılan query aşağıdaki gibi olacaktır.
update oauth.kys_role
set code='adminRole',
created_at='2023-12-26 15:57:43.713131',
updated_at='2023-12-26 15:57:43.713131',
created_by='hvltest1',
updated_by='hvltest1',
obj_version=1,
deleted=0,
deleted_at=null,
name='Updated role name',
description='Updated role description',
hierarchical=0,
editable=0,
enabled=0
where id = 51
and uuid = 'ROLE_UUID_00000000000000000000000006'
and obj_version = 0;
Dynamic update aktif durumda iken ise aşağıdaki şekilde olacaktır.
update oauth.kys_role
set name='Updated role name',
description='Updated role description',
updated_at='2023-12-27 15:04:30.211457',
obj_version=1
where id = 51
and uuid = 'ROLE_UUID_00000000000000000000000006'
and obj_version = 0;
Yukarıdaki örneklerlede de görüldüğü üzere büyük veritabanı operasyonlarında performans artışı sağlandığı gözlenebilir.
Envers#
Veritabanı işlemlerinin izini tutmak için Envers teknolojisi kullanılmaktadır. Envers, nesnenin üzerinde yapılan işlemleri nesnenin tablo isminin sonuna _AUD
eklediği tablo üzerinde tutmaktadır.
Altyapıdan gelen declarative repository kullanımı için aşağıdaki konfigurasyon eklenmelidir.
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import tr.com.havelsan.javarch.data.jpa.envers.factory.HvlEnversJpaRepositoryFactoryBean;
@EnableJpaRepositories(basePackages = {"base_package"},
repositoryFactoryBeanClass = HvlEnversJpaRepositoryFactoryBean.class)
EKSEN, envers veritabanı işlemleri için altyapı tarafından 2 tane arayüz sağlanmaktadır:
HvlEnversJpaRepository
:HvlEntity
sınıfından türeyen sınıflar için kullanılan arayüzdür.HvlEnversJpaLocalizedRepository
:HvlLocalizedEntity
sınıfından türeyen sınıflar için kullanılan arayüzdür.
hvl-infra altında bulunan 'application-database-datasource.yml, application-hvl-data.yml' dosyasıyla konfigure edilebilmektedir.
HvlEnversRevisionEntity Kullanımı#
HvlEntity gibi EKSEN entity'leri kullanılan sistemlerde envers aktif edildiği durumlarda EKSEN'e ait HvlEnversRevisionEntity
nesnesi aşağıdaki gibi kullanılarak uygun revision tablosu üretilmelidir. Aksi durumlarda uyumsuzluklar görülebilir.
package tr.com.hvl.notification.entity;
import com.querydsl.core.annotations.QueryExclude;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import org.hibernate.envers.RevisionEntity;
import tr.com.havelsan.javarch.data.jpa.envers.entity.HvlEnversRevisionEntity;
import tr.com.havelsan.javarch.data.jpa.envers.listener.HvlEnversRevisionListener;
import static tr.com.hvl.notification.entity.HvlNotificationEnversRevisionEntity.TABLE_NAME;
@QueryExclude
@Entity
@Table(name = TABLE_NAME)
@RevisionEntity(HvlEnversRevisionListener.class)
public class HvlNotificationEnversRevisionEntity extends HvlEnversRevisionEntity {
//**********************************************************
//* Table Name
//**********************************************************
public static final String TABLE_NAME = "NTF_AUDIT_REVISION";
}
Bu şekilde hibernate'in default olarak oluşturduğu revinfo
tablosu yerine, TABLE_NAME
değerinde verdiğiniz isimde bir revision tablosu oluşmuş olacaktır. Buna ek olarak _AUD
ile biten tabloların sequenceleri de EKSEN entity yapısına uygun şekilde oluşmuş olacaktır.
@Auditable
anotasyonu kullanılarak auditlenmek istenen entityler işaretlenir.
@Entity
@Table(
name = TABLE_NAME,
schema = SCHEMA_NAME
)
@Audited
@AuditOverride(forClass = HvlHardDeleteEntity.class)
public class HvlNtfNotificationMessage extends HvlHardDeleteEntity {
Liquibase kullanılan projeler için yukarıdaki örneğin liquibase xml dosyasına eklenmesi gereken kısımlar aşağıdaki gibidir.
<createTable tableName="ntf_audit_revision">
<column name="id" type="INTEGER">
<constraints nullable="false" primaryKey="true" primaryKeyName="ntf_audit_revision_pkey"/>
</column>
<column name="timestamp" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="date_updated" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="false"/>
</column>
<column name="updated_by" type="VARCHAR(255)"/>
</createTable>
<createTable tableName="ntf_notification_message_aud">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="ntf_notification_message_aud_pkey"/>
</column>
<column name="rev" type="INTEGER">
<constraints nullable="false" primaryKey="true" primaryKeyName="ntf_notification_message_aud_pkey"/>
</column>
.
. //other columns from real entity
.
</createTable>
Audit İşlemlerinin Gerçek Zamanlı Olarak Yönetilebilmesi#
EKSEN, özel envers servisleri sunarak, gerçek zamanlı olarak audit işlemlerinin aktif/pasif duruma getirilmesini sağlamaktadır. Bu 2 yöntemle desteklenmektedir. In-memory ve redis.
Dağıtık sistemlerde redis kullanımı önerilirken, daha basit küçük projelerde in-memory kullanım da tercih edilebilir.
Bağımlılıklar:
In-memory kullanım için:
Redis üzerinden kullanım için:
Varsayılan olarak tüm auditler aktif olacak şekilde düşünüldüğü için, audit özelliğinin kapatılması istenen entity sınıfının bilgisinin HvlEnversContextHolder
üzerindeki unregister
methodlarına verilmesi gerekmektedir.
try {
HvlEnversContextHolder.unregister("tr.com.havelsan.javarch.oauth.jpa.data.provider.module.user.entity.HvlOAuthUser");
} catch (HvlEnversServiceException e) {
// handle exception
}
Örnekteki kod bloğu çalıştığında HvlOAuthUser
entity'sinin audit özelliği runtime'da kapalı hale gelecektir ve sonraki işlemler için USER_AUD
tablosuna kayıt atmayacaktır.
Session Aware#
Veritabanı işlemleri sırasında mevcut session'daki kullanıcı bilgisini ve işlem yapılan tarih saat bilgisini sağlayan EKSEN bileşenidir. Bu bilgilerin veritabanı kayıtlarına otomatik olarak eklenmesi isteniyorsa bu bileşen kullanılmalıdır.
NOT: Özelleştirilmesi gereken durumlarda bu bileşen devreden çıkarılmalı ve
HvlJpaAwareProvider
arayüzü implement edilmelidir.
Redis Second Level Cache#
hvl-infra altında bulunan 'application-database-datasource.yml, redisson-slc.yaml' dosyasıyla konfigure edilebilmektedir.
Redis#
Redis'in manuel olarak kullanılması gerektiği durumlar için EKSEN tarafından sağlanan bileşendir. RedisTemplate
sınıfı bean olarak EKSEN tarafından konfigüre edilerek sağlanır.
Domain Model#
EKSEN tarafından sağlanan Entity sınıflarının sunulduğu bileşendir. Aynı zamanda UUID generator, Sequence generator, veritabanı işlemi esnasında UUID ayarlama özellikleri bu bileşenden sağlanmaktadır. hvl-data-jpa bileşeninin kullanıldığı durumlarda bu bileşen de gelmektedir.
Sağlanan Entity sınıfları şunlardır:
- HvlEntity: Temel entity sınıfıdır.
- HvlHardDeleteEntity
- HvlSoftDeleteEntity
- HvlLocalizedEntity
- HvlLookupEntity
- HvlSimpleEntity: id bilgisi içermeyen entity sınıfıdır.
Sağlanan UUID Generator strategy sınıfları şunlardır:
- HvlEntityDCESecurityBasedUUIDGeneratorStrategy
- HvlEntityDCESecurityBasedUUIDGeneratorStrategy
- HvlEntityRandomBasedUUIDGeneratorStrategy
- HvlEntityTimeBasedUUIDGeneratorStrategy
- HvlEntityTimeOrderedBasedUUIDGeneratorStrategy
- HvlEntityTimeOrderedWithMACBasedUUIDGeneratorStrategy
- HvlEntityTimeWithMACBasedUUIDGeneratorStrategy
DTO Model#
EKSEN tarafından sağlanan Model sınıflarının sunulduğu bileşendir.
Sağlanan modeller şunlardır:
- HvlModel: Temel model sınıfıdır.
- HvlLocalizedModel
- HvlLookupModel
- HvlSimpleModel: id bilgisi içermeyen modeldir.
- HvlQueryModel: Sorgulama işlemleri için kullanılan temel modeldir.
Hibernate Annotations#
EKSEN tarafından Hibernate için sağlanan özelliklerin kullanılması için oluşturulmuş olan anotasyonların sağlandığı altyapı bileşenidir. Şu anotasyonlar sağlanmaktadır:
@HvlEntitySequence
: Bu anotasyon kullanılarak özel sequence isimlendirilmesi yapılabilir. Kullanılmadığı durumlarda veritabanı nesnesindeki tablo ismi ile isimlendirme yapılır.@HvlView
: Veritabanında view olarak kullanılacak nesnelerin işaretlenmesini ve maplenmesini sağlayan anotasyondur.
JDBC Initializer#
Veritabanı ilklendirmesinin dosya üzerinden yapılmasını sağlayan bileşendir. Bu bileşen sayesinde veritabanı ilklendirmeleri için senaryolar yazılarak farklı durumlar için ilklendirmeler tetiklenebilmektedir. Senaryo dosyaları içerisinde type bilgisi sql ve ya scenario değerlerini alabilir.
Senaryo içerisindeki elementler sırayla çalışmaktadır ve çalışan elementlerin bilgisi console'a log basılmaktadır.
NOT: Hata alan bir script olursa console'a error logu basılmaktadır ancak diğer scriptler çalışmaya devam etmektedir. Bu yüzden hata durumlarında dikkatli olunmalıdır.
prod.scenario
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Scenario>
<ScenarioElement type="sql" path="[path]/uniqueConstraint.sql"/>
<ScenarioElement type="sql" path="[path]/functionalIndex.sql"/>
</Scenario>
dev.scenario
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Scenario>
<ScenarioElement type="scenario" path="[path]/prod.scenario"/>
<ScenarioElement type="sql" path="[path]/userType.sql"/>
<ScenarioElement type="sql" path="[path]/userDetail.sql"/>
<ScenarioElement type="sql" path="[path]/eventType.sql"/>
<ScenarioElement type="sql" path="[path]/user.sql"/>
</Scenario>
hvl-infra altında bulunan 'application-hvl-data.yml' dosyasıyla konfigure edilebilmektedir.
Liquibase Initializer#
Liquibase, veritabanı şeması üzerindeki değişiklikleri yönetmeyi ve versiyonlamayı sağlayan açık kaynaklı bir Java kütüphanesidir. Veritabanı şeması üzerindeki değişikliklerin izlenmesini, yönetilmesini ve uygulanmasını sağlar. Kaba tabirle veritabanı üzerinde versiyon kontrol sistemi kurmamıza yardımcı olur.
Preliquibase ise liquibase scriptlerinden önce çalışarak gerekli veritabanı şemasını oluşturmak gibi işlemleri yapmaktadır.
EKSEN, preliquibase ile varsayılan veritabanı şemalarını otomatik olarak oluşturmaktadır.
hvl-infra altında bulunan 'application-database-liquibase.yml' dosyasıyla konfigure edilebilmektedir.
Konfigürasyonları spring.liquibase
ve preliquibase
altında bulunmaktadır.
spring:
liquibase:
default-schema: ${spring.jpa.properties.hibernate.default_schema}
change-log: ${LIQUIBASE_CHANGE_LOG:db/changelog-root.yaml}
enabled: ${LIQUIBASE_ENABLED:false}
drop-first: ${LIQUIBASE_DROP_FIRST:false}
url: ${LIQUIBASE_DB_URL:${spring.datasource.url}}
user: ${LIQUIBASE_DB_USER:${spring.datasource.username}}
password: ${LIQUIBASE_DB_PASSWORD:${spring.datasource.password}}
clear-checksums: ${LIQUIBASE_CLEAR_CHECKSUMS:false}
contexts: ${LIQUIBASE_CONTEXTS:dev}
preliquibase:
default-schema: ${spring.liquibase.default-schema}
# sqlScriptReferences: classpath:/db/preliquibase/schema.sql
dbPlatformCode: postgresql
default-schema
: Liquibase scriptleri çalıştırırken şema verilmemiş scriptlerde kullanıacak varsayılan veritabanı şema bilgisidir.change-log
: Liquibase scriptleri çalıştırırken ilk olarak bakacağı change-log dosyasıdır. Bu dosyanın içerisindeki bilgilere göre sırası ile scriptler çalıştırılmaktadır.enabled
: Uygulama ayağa kalkarken liquibase'in devreye girip girmeyeceğini yöneten konfigürasyon bilgisidir.drop-first
: Uygulama ayağa kalkarken liquibase'in tüm tabloları temizleyip herşeyi en baştan çalıştırmasını sağlayan konfigürasyon bilgisidir.url
: Liquibase'in scriptlerini çalıştıracağı veritabanı url bilgisidir.user
: Liquibase'in scriptlerini çalıştıracağı veritabanının kullanıcı adı bilgisidir.password
: Liquibase'in scriptlerini çalıştıracağı veritabanının şifre bilgisidir.clear-checksums
: Liquibase'in scriptlerinde değişiklik olduğu durumda güncel scriptlerin baz alınacağını belirleyen konfigürasyon bilgisidir. Normal koşullarda liquibase çalıştırdığı bir script değişmişse hata vererek uygulamayı kapatır. Bu konfigürasyon true olduğu durumlarda mevcut checksum ı günceller ve hata atmadan devam eder.NOT: Canlı ortamlarda kurulum yapılmışsa çalışmış scriptleri güncellemek son derece tehlikelidir.
contexts
: Liquibase'in scriptlerini çalıştırırken kullanacağı context bilgisidir. Context ile belirtilmiş changeset leri bu konfigürasyon kapsamında çalıştıracaktır. Virgül kullanılarak birden fazla değer verilebilir.
Preliquibase:
default-schema
: Şemanın olmadığı durumlarda preliquibase tarafından oluşturulacak şema bilgisidir.sqlScriptReferences
: Şema oluşturulması için çalışacak script dosyasının path bilgisidir.dbPlatformCode
: Veri tabanı platform bilgisidir. Varsayılan olarak postgresql olarak ayarlanmıştır.
Proje içerisinden örnek liquibase kullanım yapısı aşağıdaki gibidir.
Model Converter#
Model ve domain nesnesi (entity) arasındaki dönüşümü sağlayan EKSEN bileşenidir. Ek olarak mapstruct için HvlGenericMapStructMapper
sınıfını sağlar.