Spring Data Nedir? Kurulumu ve Kullanımı

Paylaş

Spring framework ile Java tabanlı uygulamalarda JDBC, JPA, MongoDB, Elasticsearch gibi farklı veri kaynaklarındaki işlemleri kolay bir şekilde yapmak için kullanılan Spring Data nedir, kurulumu ve kullanımı ile ilgili bilgiler yer alıyor.

Spring Data nedir?

Spring framework ile birlikte SQL, NoSQL, LDAP, REST gibi farklı veri kaynaklarında veri ekleme, güncelleme, silme, çekme ve sorgulama gibi işlemler yapmayı kolaylaştıran bir kütüphanedir.

Spring framework hakkında detaylı bilgi almak için Spring framework yazıma bakmalısın.

Java ile veritabanından bağımsız olarak ilişkisel veritabanlarında ekleme, güncelleme, sorgulama, silme gibi işlemler yapmak için JDBC kullanılır.

Java JDBC hakkında detaylı bilgi için Java JDBC yazıma bakmanda fayda var.

JDBC kullanırken tablo, sütun ve veri türü gibi kısımların yanlış yazılması sonucu ortaya çıkan hataları gidermek için ORM(genellikle JPA) kullanılır.

Java JPA hakkında detaylı bilgi için Java JPA yazıma bakmanda fayda var.

Java ile NoSQL veya LDAP gibi farklı veri kaynaklarına erişim için çeşitli kütüphaneler kullanarak veri kaynağına ait sorgular kullanılır.

Veri kaynağı ne olursa olsun ilk olarak veri kaynağına ait sürücü/arabirim ile bağlantı sağlanarak ekleme, silme, güncelleme ve sorgulama gibi işlemler yapılıp Java içerisinde yer alan veri türüne dönüştürülür.

Spring Data bu işlemleri kolay, hızlı ve standart bir şekilde yapmak için çeşitli tasarım deseni, mimari, özellik ve soyutlamayı kullanır.

NOT: Spring Data veri kaynağına göre farklı alt projelere ayrılmıştır.

Spring Data Commons

JDBC, JPA, LDAP, MongoDB, Redis, Apache Solr, Apache Cassandra, Elasticsearch gibi farklı veri kaynaklarına ait Spring Data projeleri için yardımcı sınıf, annotation ve arayüzlerin yer aldığı en temel Spring Data paketidir.

Paket içerisinde yer alan bazı arayüz, annotation ve sınıflar;

  • Repository  – En temel Spring Data arayüzüdür.
  • CrudRepository – CRUD(Create-Read-Update-Delete) işlemleri yer alır.
  • PagingAndSortingRepository – Sayfalama ve sıralama işlemlerini yer alır.
  • QuerydslPredicateExecutor – Query DSL işlemleri yer alır.
  • Sort – Sıralama için kullanılan sınıftır.
  • Slice – Parçalama işlemleri yer alır.
  • Page – Parçalama ve sayfalama işlemleri yer alır.
  • LastModifiedBy, @LastModifiedDate vb. – Özel bilgileri ifade eder.

Spring Data projeleri Spring Data Commons paketinde yer alan sınıf ve arayüzleri kullanarak işlem yapar.

Spring Data nasıl çalışır

Spring Data Commons, veri kaynağına ait Spring Data projeleri ve veri kaynağına ait kütüphaneyi kullanarak veri kaynağı üzerinde işlem yapar.

Bu işlemleri yaparken Spring Data Commons, veri kaynağına ait arabirim ve Spring Data tarafından belirtilen kurallar(conventions, derived methods) kullanılır.

Spring Data masaüstü, web ve diğer sıradan Java uygulamalarında kullanım desteği sunar.

Spring Data kurulumu

Birçok Spring Data projesi sadece veri kaynağına ait projenin(spring-data-jpa, spring-data-mongodb vb.) pom.xml dosyasına eklenmesi yeterli olacaktır.

Maven hakkında detaylı bilgi almak için Maven yazıma bakmalısın.

NOT: Bazı projelerde veri kaynağına erişim için kullanılan arabirim kütüphanesinin ayrıca eklenmesi gerekebilir.

Örneğin; MongoDB için spring-data-mongodb kullanılır.

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-mongodb</artifactId>
    <version>4.2.5</version>
</dependency>

NOT: Spring Data alt projeleri spring-data-verikaynagi adlandırmasını kullanılır.

Spring Data kullanımı

Spring Data kullanımı veri kaynağına göre değişiklik gösterse de ilk olarak veri kaynağı bağlantısı için gerekli bean tanımlaması yapılır.

Aşağıda Spring Data Solr bağlantısına ait bean tanımlaması yapılmıştır.

@Bean
public SolrClient solrClient() {
  return new HttpSolrClient("http://localhost:8983/solr");
}

Aşağıda Spring Data MongoDB bağlantısına ait bean tanımlaması yapılmıştır.

@Bean
public MongoClient mongoClient() {
  ConnectionString connectionString = new ConnectionString("mongodb://localhost:27017/test");
  MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
    .applyConnectionString(connectionString)
    .build();
	
  return MongoClients.create(mongoClientSettings);
}

NOT: Veri kaynağı ayarlarına göre bağlantı tanımı değişiklik gösterebilir.

Veri kaynağına ait bağlantı yapıldıktan sonra verilerin tutulacağı POJO sınıfları(model, entity) oluşturulur.

Aşağıda Spring Data Solr için POJO tanımlaması yapılmıştır.

@SolrDocument
public class Product {

    @Id
    @Indexed
    private String id;

    @Indexed
    private String name;

    // getter-setter
}

Aşağıda Spring Data MongoDB için POJO tanımlaması yapılmıştır.

@Document
public class User {

    @Id
    private String id;

    @Indexed
    private String name;

    // getter-setter
}

NOT: Veri kaynağına göre POJO sınıfları(model, entity) farklılık gösterebilir.

Bağlantı ve Java sınıfları oluşturulduktan sonra Spring Data Commons veya veri kaynağı projesine özel arayüzler kullanılır.

public interface UserRepository extends MongoRepository<User, String> {}
public interface ProductRepository extends SolrCrudRepository<Product, String> {}
public interface UserRepository extends CrudRepository<User, String> {}
public interface ProductRepository extends CrudRepository<Product, String> {}

NOT: Veri kaynağı projesine ait arayüzler Spring Data Commons arayüzlerini genişleterek(kalıtarak) esnek ve fazla özellik sunar.

Gerekli tanımlamalar yapıldıktan sonra sadece arayüzde yer alan metotlar kullanılır.

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserRepository userRepository = context.getBean(userRepository.class);
userRepository.findAll().forEach(System.out::println);

NOT: En önemli özelliği veri kaynağına ait komutları yazmadan sadece Spring Data arayüzleri üzerinden işlem yapma özelliği sağlamasıdır.

Geniş kullanım desteği sağladığı için Spring Data özellikleri Spring Data JPA projesi üzerinden anlatılacaktır.

Spring Data JPA nedir?

JPA tarafından belirlenen şartları yerine getiren Hibernate, EclipseLink gibi kütüphaneleri kullanarak ilişkisel veritabanları üzerinde işlem yapmayı sağlayan Spring Data projesidir.

Spring Data JPA kurulumu

İlk olarak projeye ait kütüphane pom.xml dosyasına eklenir.

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>3.2.5</version>
</dependency>

Projenin ihtiyaç duyduğu Hibernate, EclipseLink gibi JPA sağlayıcıları eklenir.

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>6.4.4.Final</version>
</dependency>

Veritabanına ait arabirim projeye eklenerek kurulum tamamlanır.

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.2.224</version>
</dependency>

NOT: Taşınabilir olması, kuruluma ihtiyaç duymamasından dolayı H2 kullanılacaktır.

Aşağıdaki adresten farklı veri kaynaklarına ait sürücü bilgilerine ulaşabilirsiniz.

https://mvnrepository.com

NOT: Spring Data JPA içsel olarak Spring framework(core, jdbc, orm gibi) modüllerini kullanır.

Spring Data JPA kullanımı

Spring Data JPA veritabanı işlemlerinde veritabanı bağlantısının yapıldığı EntityManagerFactory sınıfına ihtiyaç duyar.

Veritabanı bağlantısı

EntityManagerFactory elde etmek için JPA içerisinde yer alan Persistence sınıfı kullanılabilir.

@Bean
public EntityManagerFactory entityManagerFactory() {
    return Persistence.createEntityManagerFactory("yusufsezerPU");
}

Sınıf varsayılan olarak veritabanı bağlantı ayarlarını META-INF/persistence.xml dosyasında arayacaktır.

Spring framework ORM modülü içersinde yer alan LocalEntityManagerFactoryBean, LocalContainerEntityManagerFactoryBean sınıfları da kullanılabilir.

@Bean
public LocalEntityManagerFactoryBean entityManagerFactory() {
    LocalEntityManagerFactoryBean lemf = new LocalEntityManagerFactoryBean();
    lemf.setPersistenceUnitName("yusufsezerPU");
    //lemf.setJpaPropertyMap(jpaProperties); // JPA ayarları
    //lemf.setJpaProperties(jpaProperties);  // JPA ayarları
    return lemf;
}

Spring tarafından sağlanan sınıflar JPA ve JPA sağlayıcılarına ait ayarları yapmayı destekler.

Spring framework ORM modülü içerisinde yer alan LocalContainerEntityManagerFactoryBean sınıfı Persistence ile LocalEntityManagerFactoryBean sınıflarına ek olarak farklı JPA XML dosyası(persistence.xml) belirleme, JPA dosyasına ihtiyaç duymadan kullanım, doğrudan JDBC DataSource kullanım gibi özellikler sunar.

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
    LocalContainerEntityManagerFactoryBean lcemf = new LocalContainerEntityManagerFactoryBean();
    lcemf.setPersistenceUnitName("yusufsezerPU");
    //lcemf.setPersistenceXmlLocation("ayarlar.xml");  // XML dosyası
    //lcemf.setDataSource(dataSource);  // JDBC DataSource
    //lcemf.setJpaVendorAdapter(jpaVendorAdapter);  // JPA sağlayıcısı
    //lcemf.setPackagesToScan("com.yusufsezer");
    return lcemf;
}

LocalContainerEntityManagerFactoryBean sınıfının en önemli özelliği JPA dosyası(persistence.xml) kullanmadan çalışabilmesidir.

Aşağıda LocalContainerEntityManagerFactoryBean sınıfının örnek kullanımı yer almaktadır.

@Configuration
@ComponentScan
@EnableJpaRepositories
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .build();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            DataSource dataSource,
            JpaVendorAdapter jpaVendorAdapter
    ) {
        LocalContainerEntityManagerFactoryBean lcemf;
        lcemf = new LocalContainerEntityManagerFactoryBean();
        lcemf.setDataSource(dataSource);
        lcemf.setJpaVendorAdapter(jpaVendorAdapter);
        lcemf.setPackagesToScan("com.yusufsezer");
        return lcemf;
    }

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter hibernateJpaVendorAdapter;
        hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
        hibernateJpaVendorAdapter.setShowSql(true);
        hibernateJpaVendorAdapter.setGenerateDdl(true);
        hibernateJpaVendorAdapter.setDatabase(Database.H2);
        return hibernateJpaVendorAdapter;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }

}

LocalContainerEntityManagerFactoryBean sınıfı tarafından kullanılacak dataSource, jpaVendorAdapter ve transactionManager tanımları yapılarak veri kaynağı bağlantısı özelleştirilmiştir.

@EnableJpaRepositories ifadesi ile JPA özellikleri aktif edilmiştir.

Sınıf cp30, HikariCP gibi farklı bağlantı havuzu(connection pool) kütüphaneleri ile kullanılabilir.

NOT: Bağlantı ayarları veri kaynağına ve istenilen sonuca göre değişiklik gösterir.

Model oluşturma

Veritabanı tablolarını Java programlama dili ile ifade etmek(maplemek) için sıradan Java sınıfları(POJO) ile JPA Entity modellemesi yapılır.

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;

    public static Person of(String firstName, String lastName) {
        Person person = new Person();
        person.setFirstName(firstName);
        person.setLastName(lastName);
        return person;
    }

    // getter-setter

}

Kullanılan @Entity ifadesi sınıfın bir JPA sınıfı olduğunu, @Id ifadesi özelliğin birincil anahtar olduğunu @GeneratedValue ifadesi ise birincil anahtarın otomatik olark üretileceğini belirtir.

Sınıf içerisindeki özelliklere(firstName, lastName) JPA ifadeleri(@Column, @Clob gibi) eklenerek özelleştirilebilir.

NOT: Getter-setter, hashCode gibi metotları hızlıca oluşturmak için Lombok kütüphanesinin kullanımı faydalı olacaktır.

Repository kullanımı

Veritabanı işlemleri için gerekli olan tanımlar yapıldıktan sonra Spring Data tarafından sağlanan repository arayüzleri kullanılır.

Temel ekleme, güncelleme, çekme, sorgulama ve silme işlemleri Spring Data Commons tarafından sağlanan CrudRepository arayüzü kullanılır.

public interface CrudRepository<T, ID> extends Repository<T, ID> {
    <S extends T> S save(S entity);
    <S extends T> Iterable<S> saveAll(Iterable<S> entities);
    Optional<T> findById(ID id);
    boolean existsById(ID id);
    Iterable<T> findAll();
    Iterable<T> findAllById(Iterable<ID> ids);
    long count();
    void deleteById(ID id);
    void delete(T entity);
    void deleteAllById(Iterable<? extends ID> ids);
    void deleteAll(Iterable<? extends T> entities);
    void deleteAll();
}

Arayüz dinamik/generic olduğundan Entity sınıfı ve birincil anahtara ait veri tipi ile kullanılır.

public interface PersonRepository extends CrudRepository<Person, Long> {}

Spring framework IoC sayesinde farklı bağlama yöntemleri(@Autowired, constructor gibi) ile kullanılabilir.

@Service
public class PersonService {

    @Autowired
    private PersonRepository personRepository;

    // diğer metotlar

}

Aşağıdaki kullanımda kurucu metot bağlama yöntemi kullanılmıştır.

@Service
public class PersonService {

    private final PersonRepository personRepository;

    public PersonService(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

}

Aşağıda örnek repository kullanımı yer almaktadır.

public class App {

    public static void main(String[] args) {

        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        PersonRepository personRepository = context.getBean(PersonRepository.class);

        Person yusuf = Person.of("Yusuf Sefa", "SEZER");
        personRepository.save(yusuf);  // veri ekleme

        personRepository.saveAll(List.of( // toplu veri ekleme
                Person.of("Ramazan", "SEZER"),
                Person.of("Sinan", "SEZER"),
                Person.of("Mehmet", "SEZER")
        ));

        yusuf.setFirstName("Yusuf");
        personRepository.save(yusuf); // veri güncelleme

        personRepository.findById(1L) // veri seçme
                .ifPresent(System.out::println);

        boolean isExists = personRepository.existsById(99L);  // veri sorgulama
        System.out.println("Kayıt " + (isExists ? "bulundu" : "bulunamadı"));

        personRepository.findAll() // veri listeleme
                .forEach(System.out::println);

        long count = personRepository.count(); // kayıt sayısı
        System.out.println(count + " kayıt yer alıyor.");

        try {
            personRepository.deleteById(33L); // veri silme
        } catch (Exception e) {
            System.err.println(e);
        }

        personRepository.deleteAll();  // toplu veri silme
    }
}

Arayüzde yer alan metotlar birçok temel veritabanı işlemlerini yapmak için yeterli olacaktır.

NOT: Spring Data JPA veritabanı işlemleri yaparken arka planda SimpleJpaRepository sınıfını kullanır.

Türetilmiş sorgu metodları – Derived query methods

Gelişmiş veri sorgulama Derived Query Methods olarak adlandırılan türetilmiş sorgu metotları ile yapılabilir.

Aşağıdaki örnekte arayüz içerisinde tanımlanan metot isimleri ve parametre değerine göre sorgu Spring Data JPA tarafından hazırlanacaktır.

public interface PersonRepository extends CrudRepository<Person, Long> {
    Iterable<Person> findByLastName(String lastName);
    Iterable<Person> findByFirstNameOrLastName(String firstName, String lastName);
}

Özellik sayesinde repository arayüzünde istenilen sonuca göre metot tanımlamak ve kullanmak yeterli olacaktır.

Iterable<Person> listSEZER = personRepository.findByLastName("SEZER");
System.out.println(listSEZER);
Iterable<Person> listYusufSEZER = personRepository.findByFirstNameOrLastName("Yusuf", "SEZER");
System.out.println(listYusufSEZER);

Özelliğin metot tanım kuralı(conventions) tabloda yer almaktadır.

Anahtar kelimeKullanımJPQL karşılığı
DistinctfindDistinctByLastnameAndFirstnameselect distinct … where x.lastname = ?1 and x.firstname = ?2
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is, EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNull, NullfindByAge(Is)Null… where x.age is null
IsNotNull, NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection<Age> ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection<Age> ages)… where x.age not in ?1
TruefindByActiveTrue()… where x.active = true
FalsefindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstname) = UPPER(?1)

Tabloda yer alan anahtar kelimeye karşılık gelen metot tanımları yapılarak sorgulama işlemi yapılır.

NOT: Metot isimleri find yerine get ile de başlayabilir.

Sınırlama – Limit

Belirli kayıt sayısını almak için findTopX, findFirstX ön eki kullanılır.

Iterable<Person> findFirst3ByLastName(String lastName);
Iterable<Person> findFirst3ByLastNameOrderByFirstNameAsc(String lastName);

Tanımlanan metotlar aşağıdaki gibi doğrudan kullanılır.

personRepository.findFirst3ByLastName("SEZER");
personRepository.findFirst3ByLastNameOrderByFirstNameAsc("SEZER");

NOT: Spring Data JPA metot isimlendirmesi ve veri kaynağına göre sorgu oluşturacaktır.

Kayıt sayısı – Count

Kayıt sayısını almak için metot isimlerinin count ile başlaması yeterlidir.

long countByLastName(String lastName);

// kullanımı
long countSEZER = personRepository.countByLastName("SEZER");
System.out.println(countSEZER);

Kayıt sayısı SQL COUNT ifadesiyle elde edilir.

Gösterim – Projections

Türetilmiş sorgu metotlarının dönüşü model sınıfı olduğundan model sınıfında yer alan tüm alanlar veri kaynağından alınacaktır.

Spring Data projections veya gösterim olarak adlandırılan özellik sayesinde sadece istenilen alanları almayı destekleyen bir özelliğe sahiptir.

Model sınıfındaki istenilen alanların yer aldığı arayüz kullanılabilir.

public interface PersonInfo {
    String getFirstName();
    String getLastName();
}

Arayüz türetilmiş metotlarda geri dönüş değeri olarak kullanılır.

Iterable<PersonInfo> findByLastName(String lastName);

Arayüz içerisinde yer alan metotlar kullanılarak istenilen bilgiye ulaşılır.

personRepository.findByLastName("SEZER")
        .forEach(p -> System.out.println(p.getFirstName() + " " + p.getLastName()));

NOT: Spring Data hazırlanan sorguda sadece arayüz içerisinde yer alan(istenilen) alanları dahil edecektir.

NOT: Çoklu ilişkiye sahip model alanları için iç-içe arayüzler kullanılabilir.

SpEL özelliği ile alanları formatlamak için @Value kullanılır.

public interface PersonInfo {
    @Value("#{target.firstName + ' ' + target.lastName}")
    String getInformation();
}

NOT: SpEL ifadesinde yer alan target model sınıfındaki alanlara erişim için kullanılır.

personRepository.findByLastName("SEZER")
        .forEach(p -> System.out.println(p.getInformation()));

Özellik sayesinde getInformation metodu formatlanmış-biçimlenmiş sonuç üretecektir.

SpEL içerisinden metot parametrelerine erişim için args anahtar kelimesi kullanılır.

public interface PersonInfo {
    @Value("#{target.firstName + ' ' + target.lastName}")
    String getInformation();
    @Value("#{args[0] + ': '+target.firstName + '\n' + args[1] + ': ' + target.lastName}")
    String getInformation2(String strFirstName, String strLastName);
}
personRepository.findByLastName("SEZER")
        .forEach(p -> System.out.println(p.getInformation()));
personRepository.findByLastName("SEZER")
        .forEach(p -> System.out.println(p.getInformation2("First name", "Last name")));
personRepository.findByLastName("SEZER")
        .forEach(p -> System.out.println(p.getInformation2("Adı", "Soyadı")));

NOT: SpEL ifadeleri kullanıldığında Spring Data sorgu optimizasyonu yapmayacaktır.

NOT: Karmaşık formatlama işlemlerinde SpEL kullanımı tavsiye edilmemektedir.

SpEL ifadeleri yerine Java 8 ile birlikte gelen default metods özelliği kullanılabilir.

public interface PersonInfo {
    String getFirstName();
    String getLastName();

    default String getInformation() {
        return String.format("%s %s", getFirstName(), getLastName());
    }

    default String getInformation2(String strFirstName, String strLastName) {
        return String.format("%s: %s%n%s: %s",
                strFirstName, getFirstName(), strLastName, getLastName());
    }

}

Java 8 hakkında detaylı bilgi almak için Java 8 yazıma bakmalısın.

Spring Data veri gösteriminde arayüze benzer DTO(Data Transfer Object) sınıflarının kullanımını da destekler.

record PersonInfo(String firstName, String lastName) {

    String getInformation() {
        return String.format("%s %s", firstName(), lastName());
    }

    String getInformation2(String strFirstName, String strLastName) {
        return String.format("%s: %s%n%s: %s",
                strFirstName, firstName(), strLastName, lastName());
    }

}

Record hakkında detaylı bilgi almak için Java 14 yazıma bakmalısın.

DTO sınıfları arayüzler gibi kullanılarak istenilen sonuç elde edilebilir.

personRepository.findByLastName("SEZER")
        .forEach(p -> System.out.println(p.getInformation()));
personRepository.findByLastName("SEZER")
        .forEach(p -> System.out.println(p.getInformation2("First name", "Last name")));
personRepository.findByLastName("SEZER")
        .forEach(p -> System.out.println(p.getInformation2("Adı", "Soyadı")));

NOT: DTO sınıfı kullanımı arayüz kullanımındaki gibi sorgu optimizasyonu yapar.

NOT: Çoklu ilişkiye sahip model alanları için iç-içe DTO sınıfları kullanılabilir.

Spring Data verilerin gösterimini dinamik olarak oluşturmak için dinamik gösterim özelliğine sahiptir.

public interface PersonRepository extends CrudRepository<Person, Long> {
    <T> Iterable<T> findByLastName(String lastName, Class<T> type);
}

Dinamik gösterim sayesinde bir model içerisindeki alanlar farklı arayüz veya sınıf(DTO) tarafından gösterimi sağlanır.

record PersonInfo(String firstName, String lastName) {

    String getInformation() {
        return String.format("%s %s", firstName(), lastName());
    }

    String getInformation2(String strFirstName, String strLastName) {
        return String.format("%s: %s%n%s: %s",
                strFirstName, firstName(), strLastName, lastName());
    }

}

Farklı arayüz tanımı yapılabilir.

public interface PersonInfo2 {
    String getFirstName();
}
personRepository.findByLastName("SEZER", PersonInfo.class)
        .forEach(p -> System.out.println(p.getInformation()));

personRepository.findByLastName("SEZER", PersonInfo2.class)
        .forEach(p -> System.out.println(p.getFirstName()));

NOT: Dinamik gösterim sayesinde her bir sınıf veya arayüz için farklı sorgulama kullanılarak sorgu optimizasyonu yapılacaktır.

Örneğe göre sorgu – Query by example

Spring Data Commons içerisinde yer alan QueryByExampleExecutor arayüzü örnek model sınıfına(POJO, Entity) göre sorgulama yapmayı sağlar.

public interface QueryByExampleExecutor<T> {
    <S extends T> Optional<S> findOne(Example<S> example);
    <S extends T> Iterable<S> findAll(Example<S> example);
    <S extends T> Iterable<S> findAll(Example<S> example, Sort sort);
    <S extends T> Page<S> findAll(Example<S> example, Pageable pageable);
    <S extends T> long count(Example<S> example);
    <S extends T> boolean exists(Example<S> example);
    <S extends T, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);
}

Arayüz metotları kalıtım alınarak kulanılır.

public interface PersonRepository extends CrudRepository<Person, Long>,
        QueryByExampleExecutor<Person> {}

Aşağıdaki özelliğe ait örnek kullanım yer almaktadır.

Person person = new Person();
person.setLastName("SEZER");
Example<Person> example = Example.of(person);

personRepository.findAll(example).forEach(System.out::println);
long countSEZER = personRepository.count(example);
System.out.println(countSEZER);

Örnek bir nesneye göre sorgulama işlemini büyük harfe, küçük harfe, and, or, like gibi ifadelere göre kullanmak için ExampleMatcher kullanılır.

Person person = new Person();
person.setFirstName("Yusuf");
person.setLastName("SEZER");
Example<Person> example = Example.of(person, ExampleMatcher.matching());  // and
Example<Person> example = Example.of(person, ExampleMatcher.matchingAll());  // and
Example<Person> example = Example.of(person, ExampleMatcher.matchingAny());  // or
Example<Person> example = Example.of(person, ExampleMatcher
        .matchingAll()
        .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) // içeren
);

Eşleşme düzenlenerek daha kesin-doğru bilgiler elde edilir.

Sıralama ve sayfalama – Sorting and pagination

Spring Data Commons tarafından sağlanan PagingAndSortingRepository arayüzü kullanılır.

public interface PagingAndSortingRepository<T, ID> extends Repository<T, ID> {
    Iterable<T> findAll(Sort sort);
    Page<T> findAll(Pageable pageable);
}

Sıralama işleminde Sort sınıfı tarafından sağlanan metotlar kullanılır.

Aşağıda sıralama özelliğine ait örnek kullanım yer almaktadır.

personRepository.findAll(Sort.by("id"))
        .forEach(System.out::println);
personRepository.findAll(Sort.by(Sort.Direction.DESC, "firstName"))
        .forEach(System.out::println);
personRepository.findAll(Sort.by("firstName").descending())
        .forEach(System.out::println);
personRepository.findAll(Sort.by("firstName", "LastName"))
        .forEach(System.out::println);
personRepository.findAll(Sort.by("firstName").and(Sort.by("lastName")))
        .forEach(System.out::println);

Sıralama işlemi sınıf türetilmiş metotlarla birlikte kullanılabilir.

Iterable<Person> findFirst3ByLastName(String lastName, Sort sort);

NOT: Türetilmiş metotlar ayrıca OrderByAlanAsc veya OrderByAlanDesc anahtar kelimeleri ile sıralama yapmayı sağlar.

Sayfalama işlemi için PageRequest sınıf tarafından sağlanan metotlar kullanılır.

PageRequest of(int page, int size);
PageRequest of(int page, int size, Sort sort);

Aşağıda sıralama özelliğine ait örnek kullanım yer almaktadır.

personRepository.findAll(PageRequest.of(0, 2))
        .forEach(System.out::println);
personRepository.findAll(PageRequest.of(0, 2, Sort.by("firstName")))
        .forEach(System.out::println);

Page<Person> persons = null;
Pageable pageable = PageRequest.of(0, 2, Sort.by("firstName"));
while (true) {
    persons = personRepository.findAll(pageable);
    int number = persons.getNumber();
    int numberOfElements = persons.getNumberOfElements();
    int size = persons.getSize();
    long totalElements = persons.getTotalElements();
    int totalPages = persons.getTotalPages();
    System.out.printf("Sayfa %s,%n"
            + "Sayfadaki eleman sayısı: %s,%n"
            + "Boyut: %s,%n"
            + "Toplam eleman sayısı: %s,%n"
            + "Toplam Sayfa sayısı: %s%n",
            number, numberOfElements, size, totalElements, totalPages);
    persons.forEach(System.out::println);
    //persons.map(p -> p.getFirstName().concat(" ").concat(p.getLastName()))
    //        .forEach(System.out::println);
    if (!persons.hasNext()) {
        break;
    }
    pageable = persons.nextPageable();
}

Sıralama işlemi için kullanılan arayüz türetilmiş metotlar ile birlikte kullanılabilir.

Page<Person> findFirst3ByLastName(String lastName, Pageable pageable);

Page arayüzü Slice arayüzünü genişleterek eleman sayısı, toplam sayfa sayısı bilgisini sağlar.

Page ve Slice arayüzü Streamable arayüzünü genişlettiğinden dolayı Streamable arayüzünde tanımlı Stream işlemlerini yapmayı sağlar.

NOT: Spring Data sayfalama işlemi sırasında eleman sayısı için ek sorgu kullanır.

Özel sorgu kullanımı – Declared queries

Türetilmiş metotlar ve örneğe göre sorgu özellikleri tarafından desteklenmeyen sorgular @NamedQuery ve @Query ifadeleri kullanılarak özel sorgular hazırlanabilir.

JPA tarafından sağlanan @NamedQuery ifadesi ile sorgu model sınıfında tanımlanır.

@Entity
@NamedQuery(name="Person.grupla",
        query ="SELECT p.lastName, COUNT(p.id) FROM Person p GROUP BY p.lastName HAVING LENGTH(p.lastName) > 2 " )
public class Person {}

Repository arayüzünde ifadenin name değerine karşılık gelen metot tanımlanır.

public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
    Iterable<Object[]> grupla();  // özel sorgu
}

Metot aşağıdaki gibi kullanılır.

personRepository.grupla()
        .forEach(p -> System.out.println(Arrays.deepToString(p)));

Spring Data JPA tarafından sağlanan @Query ifadesi metot ile tanılanarak @NamedQuery gibi kullanılır.

public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
    @Query("SELECT p.lastName, COUNT(p.id) FROM Person p GROUP BY p.lastName HAVING LENGTH(p.lastName) > 2 ")
    Iterable<Object[]> grupla();
}

@Query ifadesi veri kaynağına özel komutları çalıştırmak için nativeQuery özelliğine sahiptir.

@Query(value = "SELECT 5+5", nativeQuery = true)
int sonuc();

Komut diğer metotlarda olduğu gibi aşağıdaki gibi kullanılır.

int sonuc = personRepository.sonuc();
System.out.println(sonuc);

Kayıt sayısı ile ilgili işlemler countQuery veya countProjection ile yapılır.

Parametre kullanımı için @Param ifadesi kullanılır.

public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
    @Query("SELECT p.lastName, COUNT(p.id) FROM Person p GROUP BY p.lastName HAVING LENGTH(p.lastName) > :min ")
    Iterable<Object[]> grupla(@Param("min") int minValue);
}

Sorgu ve parametre ifadeleri aynı isimlendirmeye sahip olduklarında ayrıca @Param kullanımına gerek yoktur.

@Query("SELECT p.lastName, COUNT(p.id) FROM Person p GROUP BY p.lastName HAVING LENGTH(p.lastName) > :min ")
Iterable<Object[]> grupla(int min);

NOT: Spring Framework 6 ile birlikte parametre adı aynı olma özelliği varsayılan olarak kaldırılmıştır.

NOT: Özelliği etkileştirmek için derleyici parametresine -parameters eklenmesi gerekmektedir.

Hazırlanan metot aşağıdaki gibi kullanılır.

personRepository.grupla(4)
.forEach(p -> System.out.println(Arrays.deepToString(p)));

Spring Data özel sorgu içerisinde SpEL deyimlerini kullanmayı destekler.

@Query("SELECT p.lastName, COUNT(p.id) FROM #{#entityName} p GROUP BY p.lastName HAVING LENGTH(p.lastName) > :min ")
Iterable<Object[]> grupla(int min);

Özel sorguda kullanılan #{#entityName} ifadesi ile tablo adı dinamik olarak model sınıfından(POJO, Entity) alınır.

Model sınıfının değiştirilmesi, entity özelliğinin değiştirilmesi gibi durumlar için özelliğin kullanımı faydalı olacaktır.

Sorgu düzenleme – Modifying queries

Türetilmiş metot veya özel sorgu ile hazırlanan sorgular varsayılan olarak seçme işleminde(select) kullanılır.

@Query("UPDATE Person p SET p.firstName=:firstName WHERE p.id=:id ")
int updateFirstName(Long id, String firstName);

Sorgu hazırlanıp çalıştırıldığında hata alınacaktır.

int updateCount = personRepository.updateFirstName(1L, "Yusuf");
System.out.println(updateCount);

Varsayılanı değiştirmek için @Modifying ve @Transactional ifadeleri kullanılır.

@Modifying
@Transactional
@Query("UPDATE Person p SET p.firstName=:firstName WHERE p.id=:id ")
int updateFirstName(Long id, String firstName);

Düzenleme sonrası metot güncelleme yapacak ve güncellenen kayıt sayısının döndürecektir.

personRepository.findAll().forEach(System.out::println);
int count = personRepository.updateFirstName(1L, "Yusuf");
System.out.println(count + " kayıt değiştirildi.");
personRepository.findAll().forEach(System.out::println);

Silme işlemi güncelleme işleminden farklı olarak metot isimlendirmesi delete veya remove ile başlaması yeterli olacaktır.

@Modifying
@Transactional
int deleteByFirstName(String firstName);

Düzenleme sonrası metot silme işlemini yapacak ve silinen kayıt sayısını döndürecektir.

personRepository.findAll().forEach(System.out::println);
int count = personRepository.deleteByFirstName("Yusuf Sefa");
System.out.println(count + " kayıt silindi.");
personRepository.findAll().forEach(System.out::println);

NOT: Güncelleme ve silme bir işlem olduğundan @Transactional kullanıldığına dikkat edilmelidir.

İşlem – Transaction

Spring Data JPA SimpleJpaRepository sınıfında varsayılan olarak tüm veritabanı işlemleri @Transactional ifadesi ile transaction özelliğine sahiptir.

Transaction hakkında detaylı bilgi için A.C.I.D yazıma bakmanda fayda var.

Transaction özelliği için model sınıfına unique kısıtlaması ekleyelim.

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    @Column(unique = true)
    private String lastName;

    // getter-setter

}

Repository işlemlerini ayrı bir servis içerisinde yapalım.

@Service
public class PersonService {

    @Autowired
    private PersonRepository personRepository;

    public void kaydet() {
        personRepository.save(Person.of("Yusuf", "SEZER"));
        personRepository.save(Person.of("Ramazan", "SEZER"));
        personRepository.save(Person.of("Sinan", "SEZER"));
        personRepository.save(Person.of("Mehmet", "SEZER"));
    }

    public void listele() {
        personRepository.findAll().forEach(System.out::println);
    }

}

Varsayılan olarak gelen transaction desteği sayesinde save metodu ilk kaydı ekledikten sonra istisna fırlatacaktır.

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
PersonService personService = context.getBean(PersonService.class);
try {
    personService.kaydet();
} catch (Exception e) {
    System.err.println(e);
}
personService.listele();

Spring Data JPA metotlarda transaction özelliği kullanımını destekler.

Özelliğin kullanımı için ayar dosyasına @EnableTransactionManagement ifadesinin eklenmesi yeterli olacaktır.

@Configuration
@ComponentScan
@EnableJpaRepositories
@EnableTransactionManagement
public class AppConfig {}

@Transactional ifadesini servis sınıfındaki kaydet metodunda kullanalım.

@Transactional
public void kaydet() {
    personRepository.save(Person.of("Yusuf", "SEZER"));
    personRepository.save(Person.of("Ramazan", "SEZER"));
    personRepository.save(Person.of("Sinan", "SEZER"));
    personRepository.save(Person.of("Mehmet", "SEZER"));
}

Servis sınıfındaki kaydet metodunu çalıştırdığımızda tüm veritabanı işlemleri bir transaction olarak ele alınacak ve hata ile karşılaşıldığında geri alma işlemi(rollback) uygulanacaktır.

Saklı yordamlar – Stored procedures

Spring Data JPA veritabanı içerisinde kayıt edilen ve saklı yordam olarak adlandırılan komutları çalıştırmayı sağlar.

Saklı yordam hakkında detaylı bilgi için MySQL Saklı Yordamlar yazıma bakmanda fayda var.

MySQL Saklı Yordamlar  yazımda yer alan saklı yordamı tablomuza göre düzenleyelim.

DELIMITER //
CREATE PROCEDURE SakliYordam()
BEGIN
  SELECT * FROM person;
END//
DELIMITER ;

Saklı yordam çalıştırmak için @Query, @Procedure ve @NamedStoredProcedureQuery ifadeleri kullanılır.

@Query(value = "CALL SakliYordam()", nativeQuery = true)
Iterable<Person> SakliYordam();

// çalıştırma
personRepository.SakliYordam().forEach(System.out::println);

Saklı yordamları doğrudan kullanmak için @Procedure ifadesi kullanılır.

@Procedure("AdUzunluk")
int sonuc(String metin);

// çalıştırma
int sonuc = personRepository.sonuc("Yusuf SEZER");
System.out.println(sonuc);

Metot ve saklı yordam ismi aynı olduğunda ayrıca @Procedure ifadesinde saklı yordam belirtmeye gerek yoktur.

@Procedure
int AdUzunluk(String metin);

NOT: MySQL saklı yordamlar yazımdaki AdUzunluk kullanılmıştır.

NOT: Saklı yordam parametreleri ile metot parametre ve geri dönüş türünün aynı olduğuna dikkat edilmelidir.

Kilitleme – Locking

Aynı anda(eş zamanlı) olarak bir veri üzerinde yapılan değişiklik sonrası ortaya çıkan beklenmedik durumları yönetmek için kilitleme yöntemi kullanılır.

Spring Data JPA iyimser(optimistic) ve karamsar(pessimistic) olarak adlandırılan kilitleme yöntemlerini destekler.

İyimser kilitleme yönteminde kayıt sürümüne ait bilgi @Version ifadesi ile model sınıfında belirtilir.

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;
    @Version
    private Long version;

    // getter-setter

}

Yapılan her düzenleme sonrası kayıt sürümü değiştirilerek beklenmedik durumların önüne geçilmeye çalışılır.

personRepository.save(Person.of("Yusuf Sefa", "SEZER"));
personRepository.findAll().forEach(System.out::println);
personRepository.findById(1L)
        .ifPresent(r -> {
            r.setFirstName("Yusuf");
            personRepository.save(r);
        });
personRepository.findAll().forEach(System.out::println);

Aynı kayıt sürümünde birden fazla işlem yapılmaya çalışıldığına ilk tamamlanan işlem tabloya yansır ve diğer işlemler yapılmayıp istisna fırlatılır.

Karamsar kilitleme, iyimser kilitlemeden farklı olarak aynı anda sadece tek bir düzenlemeye izin veren veritabanı tarafından yönetilen kilitleme yöntemidir.

Kilitleme süresi JPA ayarı(javax.persistence.lock.timeout) veya veritabanı ayarı(LOCK_TIMEOUT) ile yapılır.

Gerekli olan ayarlar yapıldıktan sonra @Lock ifadesi ve LockModeType ile yapılan işleme göre kilitme seçilerek işlem yapılır.

Denetleme – Auditing

Spring Data veriler üzerinde işlem yaparken ekleme tarihi, düzenleme tarihi, kaydı oluşturan, kaydı güncelleyen ile ilgili bilgileri saklamak için Auditing olarak adlandırılan denetleme özelliğine sahiptir.

Özellik spring-aspects kütüphanesini kullanarak işlemleri yapar.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>6.1.6</version>
</dependency>

Özelliğin kullanımı için ayar dosyasına @EnableJpaAuditing ifadesinin eklenmesi yeterli olacaktır.

@Configuration
@ComponentScan
@EnableJpaRepositories
@EnableJpaAuditing
public class AppConfig {}

Kayıt ekleme tarihi için @CreatedDate ifadesi, güncelleme tarihi için @LastModifiedDate ifadesi model sınıfında kullanılır.

@EntityListeners(AuditingEntityListener.class)
public class Person {

    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;

    @CreatedDate
    private LocalDateTime createdDate;
    @LastModifiedDate
    private LocalDateTime modifiedDate;

}

NOT: Spring Data JPA ekleme ve güncelleme işlemini JPA yaşam döngüsü(@EntityListeners) ile yapar.

Optional<Person> result = personRepository.findById(1L);
try {
    Thread.sleep(5000);
} catch (Exception e) {
    System.err.println(e);
}

result.ifPresent(p -> {
    p.setFirstName("Yusuf");
    personRepository.save(p);  // kayıt güncelleniyor
});

personRepository.findAll().forEach(System.out::println);

Kaydı ekleyen için @CreatedDate ifadesi, güncelleyen için @LastModifiedDate ifadesi model sınıfında kullanılır.

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Person {

    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;

    @CreatedBy
    private String createdBy;
    @LastModifiedBy
    private String modifiedBy;

}

Spring Data JPA kayıt ekleyen veya güncelleyen ile ilgili bilgiyi almak için AuditorAware arayüzünü kullanır.

@Component
public class SystemUserAuditorAware implements AuditorAware<String> {

    @Override
    public Optional<String> getCurrentAuditor() {
        return Optional.of(System.getProperty("user.name"));
    }

}

Diğer bir yöntem ise Java 8 sürümünde gelen lambda ifadesi ile bean tanımının yapılmasıdır.

@Bean
public AuditorAware<String> auditorAware() {
    return () -> Optional.of(System.getProperty("user.name"));
}

Kayıt ekleyen ve güncelleyen ile ilgili bilgiyi işletim sistemi yerine Spring Security gibi modüller kullanılarak farklı(session, çerez) yerden alınabilir.

Şartnameler – Specifications

Spring Data JPA içerisinde yer alan JpaSpecificationExecutor arayüzü JCP tarafından belirlenen JPA şartnamelerini(Criteria API) kullanmayı sağlar.

Arayüz kalıtım alınarak aşağıdaki gibi kullanılabilir.

public interface PersonRepository extends
        PagingAndSortingRepository<Person, Long>,
        JpaSpecificationExecutor<Person> {}

Ayrıca kalıtım almak yerine JpaSpecificationExecutor ve JpaRepository(PagingAndSortingRepository,CrudRepository, QueryByExampleExecutor) arayüzlerini kalıtım alan JpaRepositoryImplementation arayüzü kullanılabilir.

public interface PersonRepository extends JpaRepositoryImplementation<Person, Long> {}

NOT: JpaRepository arayüzü Spring Data JPA projesinde temel Spring Data Commons özelliklerine ek olarak JPA özgü(flush, batch) özellikleri sağlar.

JpaSpecificationExecutor arayüzünde repository arayüzlerinde yer alan temel metotlar yer alır.

public interface JpaSpecificationExecutor<T> {
    Optional<T> findOne(Specification<T> spec);
    List<T> findAll(Specification<T> spec);
    Page<T> findAll(Specification<T> spec, Pageable pageable);
    List<T> findAll(Specification<T> spec, Sort sort);
    long count(Specification<T> spec);
    boolean exists(Specification<T> spec);
    long delete(Specification<T> spec);
    <S extends T, R> R findBy(Specification<T> spec, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);
}

Metotlar tarafından parametre olarak alınan Specification arayüzündeki toPredicate metodu ile Criteria API kullanılır.

public interface Specification<T> extends Serializable {

    static <T> Specification<T> not(Specification<T> spec) {
        return Specifications.negated(spec);
    }

    static <T> Specification<T> where(Specification<T> spec) {
        return Specifications.where(spec);
    }

    default Specification<T> and(Specification<T> other) {
        return Specifications.composed(this, other, AND);
    }

    default Specification<T> or(Specification<T> other) {
        return Specifications.composed(this, other, OR);
    }

    @Nullable
    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);

}

Aşağıda örnek kullanım yer almaktadır.

Iterable<Person> listSEZER = personRepository.findAll(new Specification<Person>() {
    @Override
    public Predicate toPredicate(Root<Person> root,
            CriteriaQuery<?> criteriaQuery,
            CriteriaBuilder criteriaBuilder) {
        // Criteria API
        return criteriaBuilder.equal(root.get("lastName"), "SEZER");
    }
});
listSEZER.forEach(System.out::println);

Java 8 ile birlikte gelen lambda ifadeleri kullanılarak aşağıdaki gibi sadeleştirilebilir.

Iterable<Person> listSEZER = personRepository.findAll((root, cq, cb) -> {
    return cb.equal(root.get("lastName"), "SEZER");
});

Criteria API kullanılarak çalışma zamanında oluşan hataların önüne geçilmiş olur.

Örnek veri – Repository populators

Spring Data veri kaynakları üzerinde işlemleri kolaylaştırmanın yanında veriler üzerinde işlem yaparken test amacıyla ihtiyaç duyulan verileri(dummy, fake) JSON veya XML ile yüklemeyi sağlar.

JSON veri yükleme işlemi için dosya aşağıdaki biçimde hazırlanır.

[
  {
    "_class": "com.yusufsezer.Person",
    "firstName": "Yusuf",
    "lastName": "SEZER"
    },
  {
    "_class": "com.yusufsezer.Person",
    "firstName": "Ramazan",
    "lastName": "SEZER"
    },
  {
    "_class": "com.yusufsezer.Person",
    "firstName": "Sinan",
    "lastName": "SEZER"
    },
  {
    "_class": "com.yusufsezer.Person",
    "firstName": "Mehmet",
    "lastName": "SEZER"
    }
]

JSON yüklenme işlemi için Spring Data Commons içerisinde yer alan Jackson2RepositoryPopulatorFactoryBean sınıfını kullanılır.

@Bean
public Jackson2RepositoryPopulatorFactoryBean repositoryPopulator() {
    Jackson2RepositoryPopulatorFactoryBean factory = new Jackson2RepositoryPopulatorFactoryBean();
    factory.setResources(new Resource[]{new ClassPathResource("veriler.json")});
    return factory;
}

Sınıf JSON yükleme işlemi için jackson-databind kütüphanesini kullanır.

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version>
</dependency>

XML veri yükleme işlemi için dosya aşağıdaki biçimde hazırlanır.

<person>
    <firstName>Yusuf</firstName>
    <lastName>SEZER</lastName>
</person>

XML yükleme işlemi için model sınıfına @XmlRootElement ifadesinin eklenmesi gerekir.

@XmlRootElement
public class Person {}

XML yüklenme işlemi için Spring Data Commons içerisinde yer alan UnmarshallerRepositoryPopulatorFactoryBean sınıfını kullanılır.

@Bean
public UnmarshallerRepositoryPopulatorFactoryBean repositoryPopulator() {
    Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    marshaller.setClassesToBeBound(Person.class);
    UnmarshallerRepositoryPopulatorFactoryBean factory = new UnmarshallerRepositoryPopulatorFactoryBean();
    factory.setUnmarshaller(marshaller);
    factory.setResources(new Resource[]{new ClassPathResource("veriler.xml")});
    return factory;
}

Sınıf XML yükleme işlemi için spring-oxm kütüphanesini kullanır.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-oxm</artifactId>
    <version>6.1.6</version>
</dependency>

Özellik örnek veriler üzerinde çalışmak, geliştirilen uygulamanın test edilmesi adımlarında faydalı olacaktır.

Web desteği – Web Support

Spring Data özelliklerini doğrudan web tabanlı uygulamalarda kullanmak için web desteğine sahiptir.

Web desteğini aktif etmek için @EnableSpringDataWebSupport ifadesi kullanılır.

@Configuration
@ComponentScan
@EnableJpaRepositories
@EnableWebMvc
@EnableSpringDataWebSupport
public class AppConfig {}

İfade Spring Data tarafından sağlanan çeşitli özellikleri(DomainClassConverter, PageableHandlerMethodArgumentResolver, SortHandlerMethodArgumentResolver vb.) aktif eder.

@RestController
public class PersonController {

    @GetMapping("/person/{id}")  // /person/1
    public Person getPersonSlug(@PathVariable("id") Person person) {
        return person;
    }

    @GetMapping("/person")  // /person?id=1
    public Person getPersonQuery(@RequestParam("id") Person person) {
        return person;
    }

}

Web desteği sayesinde /person/1, person?id=1 istekleri için Spring Data tarafından birincil anahtar değerine göre sorgu hazırlanır.

@GetMapping ifadesindeki değer(id) ile metot parametresindeki değer aynı ise @PathVariable veya @RequestParam kullanılmayabilir.

@RestController
public class PersonController {

    @GetMapping("/person/{person}")  // /person/1
    public Person getPersonSlug(Person person) {
        return person;
    }

    @GetMapping("/person") // /person?person=1
    public Person getPersonQuery(Person person) {
        return person;
    }

}

NOT: Nesnenin görüntülenebilmesi için dönüştürme(Converter) işlemine ihtiyaç olduğundan jackson-databind veya spring-oxm kütüphanesinin kullanımı faydalı olacaktır.

Spring Data PageableHandlerMethodArgumentResolver sayesinde web tabanlı uygulamalarda sayfalama ve sıralama özelliklerini kullanmayı sağlar.

Sayfalama ve sıralama özelliğini kullanabilmek için metot parametresi olarak Pageable arayüzünün kullanımı yeterli olacaktır.

@RestController
public class PersonController {

    @Autowired
    private PersonRepository personRepository;

    @GetMapping("/person")  // /person?page=0&size=10
    public ResponseEntity<?> getPerson(Pageable pageable) {
        System.out.println(pageable);
        Page<Person> page = personRepository.findAll(pageable);
        return ResponseEntity.ok(pageable.getPageSize());
    }

}

Sayfaya ?page=0&size=10 ile gelen parametrelerle sayfalar arası geçiş yapılır.

Parametresiz kullanımda varsayılan olarak açılan sayfa ve kayıt sayısını(size-20) değiştirmek için @PageableDefault(page=0, size=10) ifadesi kullanılır.

getPerson(@PageableDefault(page = 0, size = 10) Pageable pageable){}

Sayfalama ile birlikte sıralama özelliğini kullanmak için sort parametresinin adrese eklenmesi yeterli olacaktır.

person?page=0&size=10&sort=firstName,desc

İsteğinin karşılığındaki ifade aşağıdaki gibidir.

PageRequest.of(0, 10, Sort.by("firstName").descending());

Sıralamaya varsayılan değer vermek için @PageableDefault ifadesinin sort ve direction değerleri kullanılabilir.

getPerson(@PageableDefault(page=0, size=10, sort = "firstName", direction = Sort.Direction.DESC) Pageable pageable)

Spring Data SortHandlerMethodArgumentResolver sayesinde web tabanlı uygulamalarda doğrudan sıralama özelliğini kullanmayı sağlar.

@RestController
public class PersonController {

    @Autowired
    private PersonRepository personRepository;

    @GetMapping("/person")
    public ResponseEntity<?> getPerson(Sort sort) {
        System.out.println(sort);
        return ResponseEntity.ok(sort);
    }

}

Sayfaya ?sort=firstName,asc ile gelen parametrelerle sıralama yapılır.

Parametresiz kullanımda varsayılan sıralamayı değiştirmek için @SortDefault(sort = “firstName”, direction = Sort.Direction.DESC) ifadesi kullanılır.

getPerson(@SortDefault(sort = "firstName", direction = Sort.Direction.DESC) Sort sort)

Web tabanlı uygulamalarda sıklıkla kullanılan sayfalama ve sıralama özelliğinin doğrudan Spring Data ile kullanımı geliştirmeyi hızlı ve kolay hale getirmektedir.

Querydsl

Spring Data Commons içerisinde yer alan QuerydslPredicateExecutor arayüzü ile statik olarak sorgu yazmak için kullanılan Querydsl özelliğini destekler.

Arayüz kalıtım alınarak aşağıdaki gibi kullanılır.

public interface PersonRepository extends
        CrudRepository<Person, Long>,
        QuerydslPredicateExecutor<Person> {}

QuerydslPredicateExecutor arayüzünde repository arayüzlerinde yer alan temel metotlar yer alır.

public interface QuerydslPredicateExecutor<T> {
    Optional<T> findOne(Predicate predicate);
    Iterable<T> findAll(Predicate predicate);
    Iterable<T> findAll(Predicate predicate, Sort sort);
    Iterable<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);
    Iterable<T> findAll(OrderSpecifier<?>... orders);
    Page<T> findAll(Predicate predicate, Pageable pageable);
    long count(Predicate predicate);
    boolean exists(Predicate predicate);
    <S extends T, R> R findBy(Predicate predicate, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);
}

Arayüz Querydsl kütüphanesindeki Predicate arayüzünü parametre alarak işlem yapar.

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>4.4.0</version>
</dependency>
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>4.4.0</version>
</dependency>

Querydsl özelliğin kullanabilmek için ayrıca Querydsl maven eklenti ayarının yapılması gerekir.

<build>
    <plugins>
        <plugin>
            <groupId>com.mysema.maven</groupId>
            <artifactId>apt-maven-plugin</artifactId>
            <version>1.1.3</version>
            <executions>
                <execution>
                    <goals>
                        <goal>process</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>target/generated-sources/java</outputDirectory>
                        <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Querydsl JCP tarfından belirlenen JSR 269 Pluggable Annotation Processor şartnamesini kullanarak veritabanı alanlarının yer aldığı bir sınıf(QSinifAdi, QModel) oluşturur.

Sınıfın oluşturulması için projenin derlenmesi gerekir.

mvn compile

Derlendikten sonra oluşan sınıf kullanılarak statik sorgu hazırlanır.

BooleanExpression eqSEZER = QPerson.person.lastName.eq("SEZER");
personRepository.findAll(eqSEZER).forEach(System.out::println);

NOT: Querydsl özelliğini gelişmiş IDE(Netbeans, Eclipse, IntelliJ) ile kullanmak faydalı olacaktır.

Querydsl web desteği

Spring Data web desteğini aktif ettikten sonra Querydsl özelliğini web uygulamalarında kullanmayı sağlar.

Spring Data web desteğinde olduğu gibi Querydsl için sadece Predicate arayüzünü parametre olarak kullanmak yeterli olacaktır.

@RestController
public class PersonController {

    @Autowired
    private PersonRepository personRepository;

    @GetMapping("/person")
    public Iterable<Person> getPerson(Predicate predicate) {
        return personRepository.findAll(predicate);
    }

}

Spring Data /person adresine gelen isteklerde tüm verileri gösterecektir.

Querydsl kütüphanesinin Spring Data web desteğinden farkı alan bazlı sorgulama yapmaya imkan vermesidir.

Aşağıdaki gibi bir web isteğinde Querydsl QuerydslPredicateArgumentResolver sayesinde özel sorgu oluşturacaktır.

?alan=değer
?alan=değer&alan2=değer2

Yukarıda yer alan isteklere karşlık aşağıdaki sorgu oluşacaktır.

QPerson.person.alan.eq("değer");
QPerson.person.alan.eq("değer").and(QPerson.person.alan.eq("değer2"));

Querydsl tarafında desteklenen diğer sorgu parametreleri aşağıdaki gibidir.

  • ?alan=değer&alan2=değer2 – WHERE alan=değer AND alan2=değer2
  • ?alan=değer&alan=değer2 – WHERE alan IN(‘değer’, ‘değer2’)
  • ?iliskilimodel=1 – JOIN digermodel d WHERE d = ‘1’
  • ?iliskilimodel.alan=1 JOIN digermodel d WHERE d.alan = ‘1’
  • ?iliskilimodel.alan=1&iliskilimodel.alan=2 JOIN digermodel d WHERE d.alan IN(‘1’, ‘2’)

NOT: Nesnenin görüntülenebilmesi için dönüştürme(Converter) işlemine ihtiyaç olduğundan jackson-databind veya spring-oxm kütüphanesinin kullanımı faydalı olacaktır.

Querydsl web sorgularını varsayılan olarak eşittir işlemi ile sorgular.

Varsılan olarak gelen işlemi değiştirmek için QuerydslBinderCustomizer arayüzü kullanılır.

Arayüz bir sınıf ile kullanıldığında sınıf @QuerydslPredicate ile aşağıdaki gibi belirtilmesi gerekir.

getPerson(@QuerydslPredicate(bindings = MyCustomBinding.class) Predicate predicate)

Arayüz repository arayüzü ile(kalıtım alınarak) aşağıdaki gibi @QuerydslPredicate ile belirtilmeden kullanılabilir.

public interface PersonRepository extends
        CrudRepository<Person, Long>,
        QuerydslPredicateExecutor<Person>,
        QuerydslBinderCustomizer<QPerson> {

    @Override
    default void customize(QuerydslBindings querydslBindings, QPerson qPerson) {
        querydslBindings.bind(qPerson.lastName)
                .first(StringExpression::containsIgnoreCase);
    }

}

Yukarıda yer alan QuerydslBindings ile lastName alanı için gelen isteklerde parametre değerini(SQL-LIKE) içerip içermediği sorgulanacaktır.

Aşağıdaki düzenleme ile id parametresi değerinden küçük veya eşit olan değerleri getirecektir.

querydslBindings.bind(qPerson.id)
        .first(NumberExpression::loe);

Querydsl içerisinde yer alan diğer ifadeler kullanılarak istenilen sonuç elde edilir.

Diğer

Spring Data farklı veritabanı işlemleri için bir temel oluşturarak veri kaynağı ne olursa olsun kolay bir şekilde yönetmeyi sağlar.

Veriler üzerinde işlemleri yapmayı kolay hale getirse de verilerin eklenmesi, güncellenmesi, silinmesi ve sorgulanmasında kullanılan sorguların incelenmesi daha performanslı uygulamalar için faydalı olacaktır.

Spring Data projesinin altında yer alan diğer projeler genellikle Spring Data Commons tarafından belirlenen temel arayüzler üzerinde işlem yapar.

Java tabanlı uygulamalarda JDBC ve JPA ile ilişkisel veri kaynakları ile ilgili fazla uygulama geliştirildiğinden Spring Data JPA projesi diğer projelere göre daha fazla özelliğe sahip olabilir.

Uygulama geliştirirken JCP ve Jakarta/Java EE tarafından belirlenen standartları kullanmak platform değişikliği, beklenmedik değişikliklere karşı faydalı olacaktır.

Java Derslerine buradan ulaşabilirsiniz.

Hayırlı günler dilerim.


Bunlarda ilgini çekebilir


LinkedIn grubuna buradan katılın.