Gerçek Kullanım Senaryoları I: JPA/Hibernate ile Veritabanı İşlemlerinde Ekstra Scriptlerden Sakınma
JPA/Hibernate ile veritabanı işlemleri gerçekleştirirken, veritabanı işlemlerinin ne kadar önemli bir unsurla karşı karşıya olduğumuzu hepimiz biliyoruzdur. Nesnenin yeni bir kayıt mı yoksa mevcut bir kaydın güncellenmesi mi gerektiğini belirleyen ID alanının önemini göz ardı etmemeliyiz.
Bu makale, veritabanı işlemlerinde karşılaşılabilecek hatalardan kaçınmanın önemli bir parçası olan ID alanının doğru kullanımını ele alacak. ID alanının null olmaması durumunda karşılaşabileceğiniz hataları ve bu hatalardan nasıl kaçınabileceğinizi, gerçek kullanım örnekleriyle açıklayacağım.
Neden ID Alanı Önemlidir?
JPA/Hibernate, nesneleri veritabanına kaydederken, id
alanının dolu olup olmadığına ve bu id
‘ye sahip bir kaydın var olup olmadığına bakarak bu nesnenin yeni mi yoksa mevcut mu olduğunu anlar:
- ID Alanı Doluyken: JPA/Hibernate bu nesneyi mevcut bir kayıt olarak değerlendirir ve güncelleme (update) işlemi yapar.
- ID Alanı Boşken: JPA/Hibernate bu nesneyi yeni bir kayıt olarak değerlendirir ve ekleme (insert) işlemi yapar.
Yanlış Kullanım Örneği: ID Alanını Uygulama Katmanında Setlemek
import javax.persistence.*;
@Entity
@Table(name = "T_USER")
public class UserEntity {
@Id
@Column(name = "ID")
private String id;
@Column(name = "NAME")
private String name;
@ManyToMany
@JoinTable(
name = "T_USER_ROLE_RELATION",
joinColumns = @JoinColumn(name = "USER_ID"),
inverseJoinColumns = @JoinColumn(name = "ROLE_ID")
)
private List<RoleEntity> roles;
@Column(name = "CREATED_AT")
private LocalDateTime createdAt;
@PrePersist
public void prePersist() {
this.createdAt = LocalDateTime.now();
}
@PreUpdate
public void preUpdate() {
this.updatedAt = LocalDateTime.now();
}
...
}
...
List<RoleEntity> roleEntities = roleRepository.findAll();
UserEntity userEntity = new UserEntity();
userEntity.setId(UUID.randomUUID().toString());
userEntity.setName("Test User");
userEntity.setRoles(roleEntities);
userRepository.save(userEntity);
...
Bu kod parçasında,
userEntity
nesnesininid
alanına uygulama katmanında değer atanıyor.
Bu değer ataması normal şartlarda uygulama tarafında herhangi bir hataya sebep olmayabilir.
Ancak, UserEntity nesnesi ile RoleEntity nesnesi arasında n-to-n bir ilişki kurulduğunda ve @jakarta.persistence.PreUpdate
anotasyonu ile birlikte bir değer ataması yapılmak istendiğinde sorunlar ortaya çıkabilir.
Kullanıcı oluştururken ve aynı zamanda ilgili kullanıcıya rol tanımlamak istediğinizde, JPA/Hibernate id alanı dolu olduğundan bu nesnenin veritabanında zaten mevcut olduğunu düşünecek ve hem kaydetme hem de güncelleme işlemi yapacaktır.
Bu, beklenen davranıştan farklı bir duruma neden olabilir.
Yanlış kullanım sonucunda kullanıcı kaydetme işlemi yapılırken çalışan SQL sorguları:
16:29:08.941 DEBUG org.hibernate.SQL -
select
asre1_0.ID,
asre1_0.NAME,
asre1_0.CREATED_AT,
asre1_0.UPDATED_AT
from
T_USER asre1_0
where
asre1_0.ID=?
16:29:29.746 DEBUG org.hibernate.SQL -
insert
into
USER
(ID, NAME, CREATED_AT, UPDATED_AT)
values
(?, ?, ?, ?)
16:29:29.797 DEBUG org.hibernate.SQL -
update
T_USER
set
NAME=?,
CREATED_AT=?,
UPDATED_AT=?
where
ID=?
16:29:29.826 DEBUG org.hibernate.SQL -
insert
into
T_USER_ROLE_RELATION
(USER_ID, ROLE_ID)
values
(?, ?)
Doğru Kullanım Örneği: @GeneratedValue Anotasyonu Kullanmak
import java.util.UUID;
import javax.persistence.*;
@Entity
@Table(name = "T_USER")
public class UserEntity {
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@Column(name = "NAME")
private String name;
@ManyToMany
@JoinTable(
name = "T_USER_ROLE_RELATION",
joinColumns = @JoinColumn(name = "USER_ID"),
inverseJoinColumns = @JoinColumn(name = "ROLE_ID")
)
private List<RoleEntity> roles;
@Column(name = "CREATED_AT")
private LocalDateTime createdAt;
@PrePersist
public void prePersist() {
this.createdAt = LocalDateTime.now();
}
@PreUpdate
public void preUpdate() {
this.updatedAt = LocalDateTime.now();
}
...
}
Bu kod parçasında,
userEntity
nesnesininid
alanına değer atanmıyor.
Peki bu hatayı nasıl gideririz?
id
değerini uygulama katmanında manuel bir şekilde atamak yerine @jakarta.persistence.GeneratedValue
anotasyonunu kullanmalıyız.
Eğer id
alanı otomatik olarak UUID ile atanacak şekilde ayarlandıysa, id
değerini manuel olarak set etmenize gerek yoktur. Bu durumda, JPA/Hibernate otomatik olarak yeni bir UUID oluşturur ve kaydı ekler.
Örnekte, UserEntity
sınıfının id
alanı otomatik olarak UUID ile ayarlandığı için, JPA/Hibernate yeni bir UUID oluşturacak ve kaydı ekleyecektir. Bu, yeni kayıt ekleme işlemlerinde en çok tercih edilen ve doğru olan yaklaşımdır.
Doğru kullanım ile birlikte kullanıcı kaydetme işlemi yapılırken çalışan scriptler;
16:22:09.070 DEBUG org.hibernate.SQL -
insert
into
T_USER
(ID, NAME, CREATED_AT, UPDATED_AT)
values
(?, ?, ?, ?, ?, ?, ?)
16:22:09.111 DEBUG org.hibernate.SQL -
insert
into
T_USER_ROLE_RELATION
(USER_ID, ROLE_ID)
values
(?, ?)
Özet
- Yanlış: Uygulama katmanında Entity içerisindeki id alanının değerini atamak.
@jakarta.persistence.GeneratedValue
anotasyonunu kullanmamak. - Doğru:
@jakarta.persistence.GeneratedValue
anotasyonunu kullanmak ve id alanı için değer atamasının JPA/Hibernate tarafından yapılmasını sağlamak.
Bu prensipleri doğru şekilde kullanarak ve takip ederek, JPA/Hibernate kullanırken veritabanına doğru şekilde kayıt ekleyebilir veya mevcut kayıtları güncelleyebilirsiniz. Bu, uygulamanızın veritabanı işlemlerini doğru ve verimli bir şekilde yönetmesine yardımcı olacak, istenmeyen sorguların çalışmasına engel olacaktır.
Makalede yer verilen kod parçalarının gerçek kullanım senaryolarına aşağıdaki projeden erişebilirsiniz;