Thursday, January 15, 2015

Spring Data MongoDB ile Spring MongoDB entegrasyonu

Spring Data projesi ile MongoDB'ye erişmek kolay bir hal alıyor.

Kodlara GitHub üzerinden erişebilirsiniz.

spring-boot-starter-data-mongodb'yi dependency olarak ekliyoruz.
pom.xml dosyası:

    org.springframework.boot
    spring-boot-starter-parent
    1.1.10.RELEASE


    
        org.springframework.boot
        spring-boot-starter-data-mongodb
    



    
    UTF-8
    UTF-8
    org.guneriu.springboot.mongo.Application



    
        
            org.springframework.boot
            spring-boot-maven-plugin
        
    


MongoDB üzerinde CRUD işlemlerini yapabilmek için Spring-Data-MongoDB bize MongoRepository sınıfını sunuyor. Bu sınıf ile birlikte
"count, delete, delete, delete, deleteAll, exists, findAll, findOne, save" operasyonları hazır halde geliyor. Bununla birlikte bu metodlara geçirebileceğimiz "Pageable" ve "Sort" parametreleri ile paging ve sorting işlemlerini de yapabiliyoruz.

Customer sınıfı:
import org.springframework.data.annotation.Id;

public class Customer {

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

    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return String.format("Customer[id=%s, firstName='%s', lastName='%s']",
                id, firstName, lastName);
    }
}

CustomerRepository sınıfı.
import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;

public interface CustomerRepository extends MongoRepository<Customer string=""> {

    public List<Customer> findByFirstName(String firstName);
    public List<Customer> findByLastName(String lastName);

    @Query(value = "{}", count = true)
    public int customerCount();

    @Query(value = "{$or:[{'firstName': '?0'}, {'lastName': '?0'}]}", count = true)
    public int customersCountByFirstNameOrLastName(String name);
}

Buradaki findByFirstName ve findByLastName için bir kod bloğu yazmadım. Spring property expression ile sınıf üzerindeki alanlar ile metod ismini eşleştirip bizim için sorguları oluşturuyor. Aşağıdaki örnek kullanımlar Spring-Data-Mongo referans dökümanından. Bazen kompleks sorgular yazmanız gerektiğinde native sorgular yazıp çalıştırabilirsiniz. Bunun için yukarıdaki örnekte görüldüğü gibi @Query annotasyonu içerisine sorguyu yazıyoruz '?0' parametre yerine geçen placeholder.

// Enabling ignoring case for an individual property
    List<Person> findByLastnameIgnoreCase(String lastname);
    // Enabling ignoring case for all suitable properties
    List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

    // Enabling static ORDER BY for a query
    List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
    List<Person> findByLastnameOrderByFirstnameDesc(String lastname);

MongoDB üzerinde işlem yapabilmek için Spring'in sunduğu diğer sınıf ise MongoTemplate. Bu örnek için MongoRepository yeterli geliyor ancak kullanımını göstermek için MongoTemplate yapılandırması aşağıdaki gibi.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;

import com.mongodb.Mongo;
import com.mongodb.MongoClient;

@Configuration
public class MongoConfig {

    @Bean
    public Mongo mongo() throws Exception {
        return new MongoClient();
    }

    @Bean
    public MongoOperations mongoTemplate() throws Exception {
        return new MongoTemplate(mongo(), "test");
    }

}

Uygulamayı çalıştıracağımız main metodumuzu yazdığımız Application sınıfı aşağıdaki gibi.

import java.util.Arrays;
import java.util.List;

import org.guneriu.springboot.mongo.config.MongoConfig;
import org.guneriu.springboot.mongo.customer.Customer;
import org.guneriu.springboot.mongo.customer.CustomerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

@EnableAutoConfiguration
@Import(MongoConfig.class)
public class Application implements CommandLineRunner {

    @Autowired
    private CustomerRepository customerRepository;

    @Autowired
    private MongoOperations mongoOperations;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) throws Exception {

     Customer customer1 = new Customer("James", "Gosling");
     Customer customer2 = new Customer("James", "Joyce");
     Customer customer3 = new Customer("Joshua", "Bloch");

     customerRepository.save(Arrays.asList(customer1, customer2, customer3));

     List<Customer> customers = customerRepository.findAll();

     System.out.println("listing all customers");

     for (Customer customer : customers) {
        System.out.println(customer);
     }

     List<Customer> customersByFirstName = customerRepository.findByFirstName("James");

     System.out.println("customers with firstName James");

     for (Customer customer : customersByFirstName) {
        System.out.println(customer);
     }

     List<Customer> customersByFirstNameAndLastName =
            mongoOperations.find(
                    new Query(new Criteria().andOperator(
                            Criteria.where("firstName").is("Joshua"),
                            Criteria.where("lastName").is("Bloch"))),
                            Customer.class);

     System.out.println("customers with firstName Joshua and lastName Bloch");

     for (Customer customer : customersByFirstNameAndLastName) {
         System.out.println(customer);
     }

     System.out.println("all costumer counts: " + customerRepository.customerCount());
     System.out.println("customer count firstName or lastName is James :"
             + customerRepository.customersCountByFirstNameOrLastName("James"));
    }
}

Uygulamayı terminal'den spring-boot maven eklentisini kullanarak yada "Run As Java Application" diyerek çalıştırabiliriz.
Uygulamayı çalıştırdığımızda aldığımız çıktı aşağıdaki gibi.

ugur@ugur-PC:~/dev/workspace/spring-boot-mongo$ mvn spring-boot:run
listing all customers
Customer[id=54b7062544aefa551023948f, firstName='James', lastName='Gosling']
Customer[id=54b7062544aefa5510239490, firstName='James', lastName='Joyce']
Customer[id=54b7062544aefa5510239491, firstName='Joshua', lastName='Bloch']
customers with firstName James
Customer[id=54b7062544aefa551023948f, firstName='James', lastName='Gosling']
Customer[id=54b7062544aefa5510239490, firstName='James', lastName='Joyce']
customers with firstName Joshua and lastName Bloch
Customer[id=54b7062544aefa5510239491, firstName='Joshua', lastName='Bloch']
all costumer counts: 3
customer count firstName or lastName is James :2

Sunday, January 4, 2015

Constructor içerisinde polymorphic method davranışı

Uzun zamandır okumayı planladığım Thinking in Java (4th Edition) kitabına sonunda başlayabildim. Öncelikle kitap hakkında birkaç söz etmeden geçmek istemiyorum.

Keşke Java'ya bu kitap ile giriş yapmış olsaydım. Bruce Eckel Java anlatmak yerine önce Object Oriented Programming (OOP) mantığını anlatarak işe başlıyor ve kitap boyunca da bu tavrından vazgeçmeyip her zaman OOP odağında anlatmaya devam ediyor.
Ancak Software Principles çalışırken duyabileceğim "favour composition over inheritance" öğütünü inheritance'dan bahsederken anlatması çok etkileyici.
Okumanızı tavsiye ederim.

Polymorphism bölümünde constructor içerisinde override edilmiş metod çağrımına dair anlattıklarını toparlamaya çalışacağım.

Java'da static ve final (private metodlar da dolaylı olarak final'dır) metod çağrımları hariç diğer tüm çağrımlar dynamic binding (late binding, runtime binding) ile çözümlenirler. Yani compile-time'da compiler inheritance hiyararşisinde hangi alt sınıfın metodunun çağrılacağını bilmez buna runtime'da karar verir.

Peki base class constructor içerisinde polymorphic bir metodu çağırırsak hangi metod çağrılır?
Cevap alt sınıf metodu.

Bir örnek ile görelim. Sonra bu durumun yaratabileceği problemlere bakalım.

class Shape {
 
 Shape() {
  System.out.println("shape before draw method");
  draw();
  System.out.println("shape after draw method");
 }

 void draw() {
  System.out.println("shape -> draw");
 }
}

public class Circle extends Shape {
 private int radius = 0;
 
 Circle(int radius) {
  this.radius = radius;
  System.out.println("circle before draw method");
  draw();
  System.out.println("circle after draw method");
 }

 @Override
 void draw() {
  System.out.println("circle -> draw. radius: " + radius);
 }
 
 public static void main(String[] args) {
  Shape shape = new Circle(5);
 }
}

Kod çıktısı aşağıdaki gibi.

shape before draw method
circle -> draw. radius: 0
shape after draw method
circle before draw method
circle -> draw. radius: 5
circle after draw method

Shape sınıfı içerisinde draw metodu çağrımında override edilmiş alt sınıf metodu çağrıldı (2. satır). Ancak henüz Circle objesi stable durumda değil. Yine highlight edilmiş satırda görüleceği üzere Circle sınıfına radius değeri olarak 5 göndermemize rağmen henüz Shape constructor çağrımından dönmediği için Circle constructor tamamlanmadı dolayısı ile sınıfımız şu anda hatalı bir statüde duruyor.

Sonuç olarak kazara yapılacak bir hata sonucu böylesi bir bug'ı bulmak biraz zor olabilir.

Aynı örnekte radius primitive olduğu için default 0 değerini aldı. Ancak Integer tipinde bir nesne olarak tanımlayıp örneğin toString() metodunu çağırsaydık Shape sınıfı'nın constructor içerisinden gelen draw çağrımı sonucu NullPointerException alıyor olacaktık.

Bu durumdan kaçınmak için constructor'larda minimum işlem ile objeyi geçerli bir statüye getirip yapabiliyorsak sınıftaki diğer metod çağrımlarından kaçınmalıyız. Constructor'larda güvenle çağırabileceğimiz metodlar üst sınıfta final olarak tanımlananlardır. Çünkü bunların override edilme ihtimalleri olmadığından burada bahsettiğim sorun ortaya çıkmayacaktır.