Tuesday, August 23, 2016

Java 8'de Optional Kullanımı


Java'da iki tür tip bulunuyor: basit tipler ve sınıflar. Değişkeni bellekte bir kutu olarak düşünebiliriz. Basit tipten bir değişken için bu kutunun içinde tanımlandığı basit tipten bir değer yer alır. Java'daki basit tipler fazla değil: byte, short, int, long, float, double, char ve boolean. Bunun dışındaki her tip bir sınıftır. Sınıf tipinden bir değişken ise referans değişkeni olarak adlandırılır. Referans tipinden bir değişken için ise kutunun içinde bir değer değil, bir adres yer alır. Bu adres ilgili sınıftan bir nesnenin adresidir. Java'da tüm nesneler Heap'de olmak zorundadır. Bu nedenle nesnelere bazen Heap nesnesi dendiği olur. Ancak referans tipinden bir değişken için kutunun içi boş olabilir. Kutunun içinin boş olduğunu, geçerli bir nesneyi göstermediğini tanımlamak için Java dilindeki bir sözcükten yararlanıyoruz: null. Eğer kutunun içinde null değeri var ise bu değişkeni kullanmasanız iyi olur. Eğer referans değişkenimiz null değeri taşıyorsa, bu referansı kullanarak bir metot çağrısı yapmanız, yürütme zamanında NullPointerException (NPE) tipinde bir hata almanıza neden olur. 


NPE bir RuntimeException olduğu için en azından derleyici sizi bu tür bir hatayı ele almak zorunda bırakmaz. Ele almadığınız durumda ise uygulamanız istenmeyen bir şekilde sona erecektir. Kutunun içinin boş olması, referans değişkeninin değerinin null olması, bazen mantıksal bir hatanın sonucudur. Bu hatayı mutlaka gidermeniz gerekir. Kutunun içinin boş olması, bazen de işin doğası gereği olabilir. İşin doğası gereği null değeri taşıyacak ise bu referans üzerinden erişimden önce mutlaka önlem alınması gerekir:
public class MovieSearchService {
. . .
    Collection<Movie> search(Criteria criteria){ . . . }
. . .
}

MovieSearchService içindeki search() metodu verilen bir kritere göre sorgu sonucu dönüyor ve sorgu sonucu boş olabilir. Bu durumda metot null dönüyorsa sorgu sonucuna erişimden önce mutlaka önlem almak gerekir:
MovieSearchService serchSrv= InMemeoryMovieSearchService.getInstance();
Criteria someCriteria= new Criteria(/* irrelavent */); 
Collection<Movie> searchResult= searchSrv.search(someCriteria);
if (searchResult != null){
   System.out.println(String.format("There are %d movies found:",searchResult.size()));
   searchResult.stream().forEach(System.out::println)    
} 

Eğer bir metot torba dönüyorsa, uygulama programcısını NPE'ye neden olmayacak ancak yukarıdaki gibi bir önlem almaya zorlamayacak bir çözüm sunabiliriz. Bunun için eğer arama kriterine uygun hiç bir film yoksa basitçe içi boş bir liste dönebiliriz: Collections.emptyList(). Bunun yanında, Java 8 ile birlikte doğrudan torba dönmek yerine Stream<> ya da CompletableFuture<> dönülebilir:
public interface ManagedServerRepository extends Repository<ManagedServer,Long> {
     Optional<S extends ManagedServer> findOne(Long id);
     <S extends ManagedServer> S save(S server);
     <S extends ManagedServer> S delete(S server);
     <S extends ManagedServer> S delete(Long id);
     @Query("select m from ManagedServer m")
     Stream<ManagedServer> streamAllServers();
     Stream<ManagedServer> findByServerType(ServerType serverType);
     @Async
     CompletableFuture<List<ManagedServer>> findAll();
}

Stream<> ile uyuşuk işleme (=lazy processing) ve CompletableFuture<> ile reaktif fonksiyonel programlama (=reactive functional programming) yapılabilir. Yukarıdaki kodda Spring Data JPA kullanılmaktadır. Spring Data JPA tabanlı kalıcılık çözümünde, sadece kalıcılık işlemlerinin tanımlandığı bir -arayüz tanımlanır, gerçekleme verilmez. Tüm kalıcılık detayları ile Spring Data JPA çatısı ilgilenecektir. Spring Data JPA çatısı Java Persistence API (JPA) çatısı üzerine kuruludur.
Burada tartışılabilecek diğer bir çözüm, arama kriterine uyan hiç bir film bulunamadığında, bunu bir hata durumu olarak kabul edip hatayı açıklayan bir Exception nesnesi fırlatmak olabilir. Bu durumda metot kritere uyan en az bir film bulduğunda bir torba döner ya da sorgu sonucu boş ise Exception nesnesi fırlatır. Her iki çözüm de herhangi bir önlem alınmış olunsun ya da olunmasın NPE'ye neden olmaz. Bu çözümden hangisinin tercih edileceği işin sahibinin vermesi gereken bir karardır. Eğer genel bir API tasarlanıyor ise API'nin her iki çözümü de içermesi tercih edilir. Kararı bırakın çağıran taraf kendi ihtiyacına göre versin.

Nesneyi Optional ile Sarmallamak

Java 8 ile birlikte üçüncü bir çözüm daha geldi: Optional. Optional sınıfı bir nesneyi saklama ve erişmek için bir çözüm sunuyor. Eğer saklayacağı nesne null olabiliyor ise Optional.ofNullable metodu ile yaratıyoruz:
@Override
public Optional<Country> findCountryByCode(String code) {
 Country country = countries.get(code);
 return Optional.ofNullable(country);
}

Eğer nesnenin null olmaması gerekiyorsa değeri Optional.of() metodu ile sarmallıyoruz:
Optional<Double> average= Optional.of(Double.NaN);

Optional.of() metodu null kontrolü yapar ve parametre null ise NPE fırlatır:
public final class Optional<T> {

   . . .

   public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
   }

    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }

    . . .

}

 
public final class Objects {

    . . .

   public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }   

    . . .

}

Eğer sarmallanacak değer null ise bunu doğrudan Optional.empty() çağrısı ile oluşturabiliriz:
Optional<Double> average= Optional.empty(); 

Optional ile Nesneye Ulaşmak

Optional içinde sakladığımız nesneye ulaşmak için yukarıda tartıştığımız şekillerde tasarlanmış farklı davranışlara sahip metotlar bulunuyor. get() metodu ile Optional'da saklanan nesnenin referansına ulaşırsınız. Ancak Optional içinde nesne yoksa NoSuchElementException() alırsınız:
public final class Optional<T> {

    . . .

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    . . .

}

NoSuchElementException yine bir RuntimeException türünde bir hatadır. Collection API içindeki Iterator arayüzündeki next() metodu da eğer torbada ziyaret edilecek başka bir eleman yoksa NoSuchElementException fırlatır. Bu hatayı almamak için Optional içinde bir değer olup olmadığını sorgulayabilirsiniz:
Optional<Country> country= countryDao.findCountryByCode("KLM");
if (country.isPresent())
    System.out.println(country.get().getName()); 

Optional içinde yukarıdaki çözümü daha yalın bir şekilde karşılayacak  başka bir metot yer alıyor:
Consumer<Country> countryNameConsumer = c -> System.out.println(c.getName());
country.ifPresent(countryNameConsumer);

Yukarıdaki çözümü daha da yalın ifade edebiliriz:
country.map(Country::getName).ifPresent(System.out::println);

Eğer Optional içinde değer yoksa alternatif bir değerin kullanılmasını isteyebiliriz:
String countryName= country.map(Country::getName).orElse("Not found!");

Şimdi orElse() metodunun Optional sınıfındaki tanımına bakalım:
public final class Optional<T> {
    . . .
    public T orElse(T other) {
        return value != null ? value : other;
    }
    . . .
}

Eğer aradığımız kodda bir ülke yoksa yeni bir ülke yaratmak için Optional sınıfındaki orElseGet() çağrısını kullanıyoruz:
CountryDao countryDao= InMemoryWorldDao.getInstance();
Optional<Country> country= countryDao.findCountryByCode("KLM");
Country found= country.orElseGet(Country::new);

Eğer aradığımız kodda bir ülke yoksa Exception fırlatmak istersek Optional sınıfındaki orElseThrow() çağrısını kullanabilirsiniz:
CountryDao countryDao= InMemoryWorldDao.getInstance();
Optional<Country> country= countryDao.findCountryByCode("KLM");
Country existing= country.orElseThrow(NoSuchElementException::new);

Son olarak Optional içindeki fonksiyonel programlamaya yönelik flatMap() metodunu çalışacağız:
CountryDao countryDao= InMemoryWorldDao.getInstance();
Optional<Country> country= countryDao.findCountryByCode("TUR");
country.flatMap(Country::getCities).ifPresent(System.out::println);

Yukarıdaki örnekte ülke kodu ile aradığımız ülkenin şehirlerini listelemek istiyoruz. Yukarıdaki kodu anlamak için Country sınıfındaki getCities() metodunun tanımına göz atmamız gerekir:
public class Country {

     . . .

     public Optional<List<City>> getCities() {
         return Optional.ofNullable(cities);
     }

     . . .

}

Dikkat ederseniz getCities() metodu Optional<List<City>> tipinden bir değer dönüyor. Böylelikle flatMap() metodu Optional<Country>'yi Optional<List<City>>'ye dönüştürüyor.

Modellemede Optional Kullanımı

Nesneye dayalı tasarımda Optional<> ile neleri sarmallamalıyız? Sınıf özniteliklerini (=attributes, fields) ve metod parametrelerini olduğu gibi bırakın, Optional<> içinde sarmallamaya çalışmayın. Optional sınıfının en doğru kullanımı, yukarıdaki örneklerde olduğu gibi metod dönüş değerleri olacaktır. Çağıran tarafta ise Optional ile sarmallanan değerlere get() yerine orElse() ile ulaşmaya çalışın.

Java 9'da Optional'daki Yenilikler

Önümüzdeki yıl Mart ayında Java SE'nin yeni sürümü 9'un çıkması planlanıyor. Java 9Optional sınıfına yeni üç metot tanıştırıyor. Şimdi bu yenilikleri örneklerle çalışalım.  TUR, USA, ITA ve FRA kodlu ülkelerin bilgilerini ekrana yazmaya çalışalım. Java 9 öncesinde bunu aşağıdaki kodla gerçekleştirebiliriz:
Stream.of("TUR","USA","ITA","FRA")
        .map(countryDao::findCountryByCode)
        .map(c -> c.isPresent() ? Stream.of(c.get()) : Stream.empty())
        .forEach(System.out::println);

Java 9'daki ilk yenilik yukarıdaki c -> c.isPresent() ? Stream.of(c.get()) : Stream.empty() ifadesinin kısa yolunu oluşturan stream() çağrısı:
Stream.of("TUR","USA","ITA","FA")
        .map(countryDao::findCountryByCode)
        .map(Optional::stream)
        .forEach(System.out::println);

Gerçekten de Optional sınıfında stream çağrısının gerçeklemesine baktığımızda bu kısa yolu görüyoruz:
/**
 * If a value is present return a sequential {@link Stream} containing only
 * that value, otherwise return an empty {@code Stream}.
 *
 * @apiNote This method can be used to transform a {@code Stream} of
 * optional elements to a {@code Stream} of present value elements:
 *
 * <pre>{@code
 *     Stream<Optional<T>> os = ..
 *     Stream<T> s = os.flatMap(Optional::stream)
 * }</pre>
 *
 * @return the optional value as a {@code Stream}
 * @since 1.9
 */
public Stream<T> stream() {
 if (!isPresent()) {
  return Stream.empty();
 } else {
  return Stream.of(value);
 }
}

Diğer yeni gelen metot ise ifPresentOrElse(). Aşağıdaki örnekte hatalı ülke kodlarını saymak, hatasız ülke kodlarına karşı gelen ülke bilgilerini ise ekrana yazmak istiyoruz:
AtomicInteger counter= new AtomicInteger(0);
Stream.of("TUR","USA","ITA","FA")
      .map(countryDao::findCountryByCode)
      .forEach(
           c -> c.ifPresentOrElse(
                   System.out::println,
                   () -> { counter.incrementAndGet(); } 
                )
       );
System.out.println(counter.get());

Bu uygulama çalıştırıldığında aşağıdaki ekran ekran görüntüsü ile karşılaşılır:
Country{gnp=210721.0, code='TUR', name='Turkey', continent='Asia', surfaceArea=774815.0, population=66591000}
Country{gnp=8510700.0, code='USA', name='United States', continent='North America', surfaceArea=9363520.0, population=278357000}
Country{gnp=1161755.0, code='ITA', name='Italy', continent='Europe', surfaceArea=301316.0, population=57680000}
1

Sonuncu ve daha çok ihtiyaç duyulan metot ise or(). Şimdi, TUR kodlu ülkeyi önce cep (=cache) servisinde , orada yoksa InMemoryWorldDao servisinde arayalım, orada da yoksa en son JdbcWorldDao servisinde arayalım:
Optional<Country> turkey= cache.findCountryByCode("TUR")
                                .or(() -> countryDao.findCountryByCode("TUR"))
                                .or(() -> jdbcDao.findCountryByCode("TUR"));

Sunday, August 7, 2016

World Veritabanının JPA 2.1 Modeli


   MySQL açık kaynak kodlu, (Oracle, MariaDB, Percona gibi) firmalardan desteğini alabileceğiniz, yaygın ve çok ölçekli (Facebook, Google, Twitter, Linkedin ve Alibaba gibi) kullanımı olan ilişkisel bir veri tabanıdır. Oracle tarafından sunulan MySQL eğitimlerinde ve sertifikasyon sınavındaki sorularda sıklıkla kullanılan bir veri tabanı vardır: World. World veritabanını bu bağlantıdan indirebilirsiniz. Veritabanının kurulumunu, aşağıdaki adımları takip ederek kolaylıkla tamamlayabilirsiniz:
mysql> set session autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> create database world;
Query OK, 1 row affected (0.00 sec)

mysql> use world
Database changed
mysql> source c:/tmp/world.sql

. . . . . . . . . . . . . . . . . . .
Query OK, 1 row affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Query OK, 0 rows affected (0.02 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

   world veri tabanında dünya ülkeleri, şehirler ve konuşulan diller ile ilgili bilgiler üç tabloda toplanmıştır:
mysql> use world
Database changed
mysql> show tables;
+-----------------+
| Tables_in_world |
+-----------------+
| city            |
| country         |
| countrylanguage |
+-----------------+
3 rows in set (0.00 sec)

mysql> desc city;
+-------------+----------+------+-----+---------+----------------+
| Field       | Type     | Null | Key | Default | Extra          |
+-------------+----------+------+-----+---------+----------------+
| ID          | int(11)  | NO   | PRI | NULL    | auto_increment |
| Name        | char(35) | NO   |     |         |                |
| CountryCode | char(3)  | NO   | MUL |         |                |
| District    | char(20) | NO   |     |         |                |
| Population  | int(11)  | NO   |     | 0       |                |
+-------------+----------+------+-----+---------+----------------+
5 rows in set (0.01 sec)

mysql> desc country;
+----------------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
| Field          | Type                                                                                  | Null | Key | Default | Extra |
+----------------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
| Code           | char(3)                                                                               | NO   | PRI |         |       |
| Name           | char(52)                                                                              | NO   |     |         |       |
| Continent      | enum('Asia','Europe','North America','Africa','Oceania','Antarctica','South America') | NO   |     | Asia    |       |
| Region         | char(26)                                                                              | NO   |     |         |       |
| SurfaceArea    | float(10,2)                                                                           | NO   |     | 0.00    |       |
| IndepYear      | smallint(6)                                                                           | YES  |     | NULL    |       |
| Population     | int(11)                                                                               | NO   |     | 0       |       |
| LifeExpectancy | float(3,1)                                                                            | YES  |     | NULL    |       |
| GNP            | float(10,2)                                                                           | YES  |     | NULL    |       |
| GNPOld         | float(10,2)                                                                           | YES  |     | NULL    |       |
| LocalName      | char(45)                                                                              | NO   |     |         |       |
| GovernmentForm | char(45)                                                                              | NO   |     |         |       |
| HeadOfState    | char(60)                                                                              | YES  |     | NULL    |       |
| Capital        | int(11)                                                                               | YES  |     | NULL    |       |
| Code2          | char(2)                                                                               | NO   |     |         |       |
+----------------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
15 rows in set (0.01 sec)

mysql> desc countrylanguage;
+-------------+---------------+------+-----+---------+-------+
| Field       | Type          | Null | Key | Default | Extra |
+-------------+---------------+------+-----+---------+-------+
| CountryCode | char(3)       | NO   | PRI |         |       |
| Language    | char(30)      | NO   | PRI |         |       |
| IsOfficial  | enum('T','F') | NO   |     | F       |       |
| Percentage  | float(4,1)    | NO   |     | 0.0     |       |
+-------------+---------------+------+-----+---------+-------+
4 rows in set (0.01 sec)

   Bu tablolar arasındaki bağlantıyı varlık-ilişki çizgesi ile görsel olarak daha kolay kavrayabiliriz:

   Java Persistence API (JPA) ile tabloları, tablolar arasındaki yukarıdaki çizgede tanımlanan ilişkileri ile birlikte Java sınıflarına karşı düşürebilir, nesneye dayalı yaklaşımla modelleyebiliriz:

Country.java:
package com.example.world.entity;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.NamedAttributeNode;
import javax.persistence.NamedEntityGraph;
import javax.persistence.NamedEntityGraphs;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.NamedSubgraph;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;

@Entity
@NamedQueries({ 
  @NamedQuery(
        name = "AllFromCountry", 
        query = "select c from Country c"
  ),
  @NamedQuery(
        name = "ByContinentFromCountry", 
        query = "select c from Country c where c.continent=:continent"
  ) 
})
@NamedEntityGraphs({
  @NamedEntityGraph(
        name = "graph.Country.cities", 
        attributeNodes = @NamedAttributeNode(value = "cities", subgraph = "cities") , 
        subgraphs = @NamedSubgraph(
               name = "cities", 
               attributeNodes = @NamedAttributeNode("country") 
        ) 
  ),
  @NamedEntityGraph(
        name = "graph.Country.citylangs", 
        attributeNodes =  
           { 
             @NamedAttributeNode(value = "cities", subgraph = "cities") ,
             @NamedAttributeNode(value = "languages", subgraph = "languages")              
           },
        subgraphs = {
             @NamedSubgraph(
                    name = "cities", 
                    attributeNodes = @NamedAttributeNode("country")
             ), 
             @NamedSubgraph(
                    name = "languages", 
                    attributeNodes = @NamedAttributeNode("country")
             ), 
        }                   
  ),
  @NamedEntityGraph(
        name = "graph.Country.languages", 
        attributeNodes = @NamedAttributeNode(value = "languages", subgraph = "languages") , 
        subgraphs = @NamedSubgraph(
                name = "languages", 
                attributeNodes = @NamedAttributeNode("country") 
        ) 
  ) 
})
public class Country implements Serializable {
 @Id
 private String code;
 private String name;
 private int population;
 @Column(name = "surfacearea")
 private double surfaceArea;
 private String continent;

 @JoinColumn(name = "capital", nullable = false, updatable = false, insertable = false)
 @OneToOne(cascade={CascadeType.MERGE})
 private City capital;

 @OneToMany(mappedBy = "country",orphanRemoval=true)
 private Set<City> cities;

 @OneToMany(mappedBy = "country",orphanRemoval=true)
 private Set<CountryLanguage> languages;

 public Country() {
 }

 // getters and setters

 @Override
 public String toString() {
  return "Country [code=" + code + ", name=" + name + ", population=" + population + ", surfaceArea="
    + surfaceArea + ", continent=" + continent + ", capital=" + capital + ", cities=" + cities
    + ", languages=" + languages + "]";
 }


}

City.java:
package com.example.world.entity;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToOne;

@Entity
@NamedQueries({
 @NamedQuery(name="fromCity.all",query="select c from City c"),
 @NamedQuery(name="fromCity.byCountry",query="select c from City c where c.country.code=:code")
})
public class City {
 @Id
 private int id;
 private String name;
 private Integer population;

 @JoinColumn(name = "countrycode", nullable = true,insertable=false,updatable=false)
 @OneToOne(fetch = FetchType.LAZY)
 private Country country;

 public City() {
 }

 // getters and setters

 @Override
 public String toString() {
  return "City [id=" + id + ", name=" + name + ", population=" + population + ", country name="
    + country.getName() + "]";
 }

}

CountryLanguagePK.java:
package com.example.world.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Embeddable;

@Embeddable
public class CountryLanguagePK implements Serializable {
 @Column(nullable=false)
 private String language;
 @Column(name = "countrycode",nullable=false)
 private String code;

 public CountryLanguagePK() {
 }

 public CountryLanguagePK(String language, String code) {
  this.language = language;
  this.code = code;
 }

 // getters and setters

 @Override
 public String toString() {
  return "CountryLanguagePK [language=" + language + ", code=" + code + "]";
 }

}

CountryLanguage.java:
package com.example.world.entity;

import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;

import com.example.world.entity.converter.BooleanCharacterConverter;

@Entity
public class CountryLanguage {
 @EmbeddedId
 private CountryLanguagePK countryLanguagePK;
 @Column(name = "isOfficial")
 @Convert(converter = BooleanCharacterConverter.class)
 private boolean official;
 private double percentage;

 @OneToOne()
 @JoinColumn(name = "countrycode", insertable = false, updatable = false, nullable = false)
 private Country country;

 public CountryLanguage() {
 }

 // getters and setters

 @Override
 public String toString() {
  return "CountryLanguage [countryLanguagePK=" + countryLanguagePK + ", official=" + official + ", percentage="
    + percentage + "]";
 }

}

BooleanCharacterConverter.java:
package com.example.world.entity.converter;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter
public class BooleanCharacterConverter implements AttributeConverter<Boolean, String>{

 @Override
 public String convertToDatabaseColumn(Boolean value) {
  return value ? "T" : "F";
 }

 @Override
 public Boolean convertToEntityAttribute(String value) {
  return "T".equals(value);
 }


}

   Java sınıflarını, veri tabanındaki tablolara karşı düşürürken, Java programlama diline Java SE 5 ile gelen notlardan (=annotation) yararlanıyoruz. Mutlaka kullanmamız gereken iki not bulunuyor: @Entity ve @Id. @Entitiy notu ile sınıfın kalıcı olması gereken ve yaşam döngüsünün JPA tarafından yönetilen bir sınıf olduğunun notunu düşmüş oluyoruz. Her ne kadar veri tabanında her tabloda bir birincil anahtar bulunma zorunluluğu bulunmasa da eğer JPA ile çalışıyorsanız tablonun mutlaka bir ya da daha fazla alandan oluşan bir birincil anahtarı (=primary key) bulunması gerekir. Bu birincil anahtara karşı düşen öz niteliğe @Id notunu düşüyoruz. Eğer birincil anahtar birden fazla alandan oluşuyor ise buna bileşik birincil anahtar (=composite primary key) adını veriyoruz. Bileşik birincil anahtarları iz düşürürken kullanılan yöntemlerden biri, ilk olarak, bu birden fazla birincil anahtar için iz düşüreceğimiz öz niteliklerin olduğu ayrı bir sınıf oluşturmak ve bu sınıfa @Embeddable notunu düşmektir. Daha sonra bu sınıftan bir öz nitelik, @Entity notunu düştüğümüz sınıfta @EmbeddedId notu ile tanımlanır. Bunun bir uygulamasını CountryLanguagePK ve CountryLanguage sınıflarında bulabilirsiniz.
   JPA'da tüm kalıcılık işlemleri için EntityManager arayüzünü kullanıyoruz. JPA aslında kalıcılık problemini çözen bir kod içermiyor. Kalıcılık problemi ile ilgilenen çözümler ile uygulama kodumuz arasında bir arayüz sağlıyor. Bu nedenle JPA tabanlı çözümde mutlaka bir JPA gerçeklemesine gereksinim duyarız. Bu gereksinimi Hibernate, Eclipselink ya da OpenJPA gibi kütüphaneler ile karşılayabiliriz. Maven kullananlar için bu kütüphanelerin bağımlılıklarını pom.xml'e eklemeniz gerekir:
  • Hibernate 5.2 için
<dependency>
 <groupId>org.hibernate</groupId>
 <artifactId>hibernate-core</artifactId>
 <version>5.2.1.Final</version>
</dependency>
  • EclipseLink 2.6 için
<dependency>
 <groupId>org.eclipse.persistence</groupId>
 <artifactId>eclipselink</artifactId>
 <version>2.6.3</version>
</dependency>

JPA konfigürasyonu için adı ve yeri standart olan bir dosya bulunuyor: META-INF/persistence.xml:
  • Hibernate 5 kullananlar için
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
 xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
 <persistence-unit name="worldPU" transaction-type="RESOURCE_LOCAL">
                <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
         <class>com.example.world.entity.Country</class>
         <class>com.example.world.entity.City</class>
         <class>com.example.world.entity.CountryLanguage</class>
         <class>com.example.world.entity.CountryLanguagePK</class>
         <class>com.example.world.entity.converter.BooleanCharacterConverter</class>
         <properties>
          <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/world" />
          <property name="javax.persistence.jdbc.user" value="root" />
          <property name="javax.persistence.jdbc.password" value="root" />
          <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
          <property name="hibernate.show_sql" value="true" />
          <property name="hibernate.format_sql" value="true" />
          <property name="hibernate.enable_lazy_load_no_trans" value="true" /> 
         </properties>
 </persistence-unit>
</persistence>
  • EclipseLink 2.6 kullananlar için
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
 xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
 <persistence-unit name="worldPU" transaction-type="RESOURCE_LOCAL">
  <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
  <class>com.example.world.entity.Country</class>
  <class>com.example.world.entity.City</class>
  <class>com.example.world.entity.CountryLanguage</class>
  <class>com.example.world.entity.CountryLanguagePK</class>
  <class>com.example.world.entity.converter.BooleanCharacterConverter</class>
  <properties>
   <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/world" />
   <property name="javax.persistence.jdbc.user" value="root" />
   <property name="javax.persistence.jdbc.password" value="root" />
   <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
   <property name="eclipselink.logging.level.sql" value="FINE" />
   <property name="eclipselink.logging.parameters" value="true" />
  </properties>
 </persistence-unit>
</persistence>

Şimdi kalıcılık işlemlerini tanımlayacağımız Data Access Object (DAO) arayüzlerini ve bu arayüzleri EntityManager kullanarak gerçekleyeceğimiz sınıfları kodlayalım:

GenericDao.java:
package com.example.world.dao;

import java.util.Collection;

public interface GenericDao<Entity,Key> {
 Entity add(Entity country);
 Entity update(Entity country);
 Entity remove(Key key);
 Entity find(Key key);
 Entity find(Key key,String graphName);
 Collection<Entity> findAll();
}

CountryDao.java:
package com.example.world.dao;

import java.util.Collection;

import com.example.world.entity.Country;

public interface CountryDao extends GenericDao<Country, String> {
 Collection<Country> findByContinent(String continent);
}

CityDao.java:
package com.example.world.dao;

import java.util.Collection;

import com.example.world.entity.City;

public interface CityDao extends GenericDao<City, Integer>{
 Collection<City> findCitiesByCountry(String code);
}

CountryLanguageDao.java:
package com.example.world.dao;

import com.example.world.entity.CountryLanguage;
import com.example.world.entity.CountryLanguagePK;

public interface CountryLanguageDao extends GenericDao<CountryLanguage, CountryLanguagePK> {
}

JpaCountryDao.java:
package com.example.world.dao.impl;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.EntityGraph;
import javax.persistence.EntityManager;

import com.example.world.dao.CountryDao;
import com.example.world.entity.Country;

public class JpaCountryDao implements CountryDao {

 private EntityManager entityManager;

 public void setEntityManager(EntityManager entityManager) {
  this.entityManager = entityManager;
 }

 @Override
 public Country add(Country country) {
  entityManager.persist(country);
  return country;
 }

 @Override
 public Country update(Country country) {
  entityManager.merge(country);
  return country;
 }

 @Override
 public Country remove(String code) {
  Country found = entityManager.find(Country.class, code);
  if (found != null) {
   entityManager.remove(found);
  }
  return found;
 }

 @Override
 public Country find(String code) {
  return entityManager.find(Country.class, code);
 }

 @Override
 public Collection<Country> findAll() {
  return entityManager.createNamedQuery("AllFromCountry", Country.class).getResultList();
 }

 @Override
 public Collection<Country> findByContinent(String continent) {
  return entityManager.createNamedQuery("ByContinentFromCountry", Country.class)
    .setParameter("continent", continent).getResultList();
 }

 @Override
 public Country find(String key, String graphName) {  
  EntityGraph<?> eg= entityManager.getEntityGraph(graphName);
  Map<String,Object> props= new HashMap<>();
  props.put("javax.persistence.fetchgraph", eg);
  return entityManager.find(Country.class, key, props);
 }

}

JpaCityDao.java:
package com.example.world.dao.impl;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityGraph;
import javax.persistence.EntityManager;

import com.example.world.dao.CityDao;
import com.example.world.entity.City;

public class JpaCityJpaDao implements CityDao {
 private EntityManager entityManager;

 public void setEntityManager(EntityManager entityManager) {
  this.entityManager = entityManager;
 }

 @Override
 public City add(City country) {
  entityManager.persist(country);
  return country; 
 }

 @Override
 public City update(City country) {
  return entityManager.merge(country);
 }

 @Override
 public City remove(Integer key) {
  City city;
  city = entityManager.find(City.class, key);
  if (city != null) {
   entityManager.remove(city);
  }
  return city;
 }

 @Override
 public City find(Integer key) {
  City city = entityManager.find(City.class, key);
  return city;
 }

 @Override
 public Collection<City> findAll() {
  List<City> cities = 
    entityManager.createNamedQuery("fromCity.all", City.class)
                 .getResultList();
  return cities;
 }

 @Override
 public Collection<City> findCitiesByCountry(String code) {
  List<City> cities = entityManager.createNamedQuery("fromCity.byCountry", City.class)
                             .setParameter("code", code)
                             .getResultList();
  return cities;
 }

 @Override
 public City find(Integer key, String graphName) {
  EntityGraph<?> eg= entityManager.getEntityGraph(graphName);
  Map<String,Object> props= new HashMap<>();
  props.put("javax.persistence.fetchgraph", eg);
  return entityManager.find(City.class, key, props);
 }

}

JpaCountryLanguageDao.java:
package com.example.world.dao.impl;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityGraph;
import javax.persistence.EntityManager;

import com.example.world.dao.CountryLanguageDao;
import com.example.world.entity.CountryLanguage;
import com.example.world.entity.CountryLanguagePK;

public class JpaCountryLanguageDao implements CountryLanguageDao {

 private EntityManager entityManager;
 
 public void setEntityManager(EntityManager entityManager) {
  this.entityManager = entityManager;
 }

 @Override
 public CountryLanguage add(CountryLanguage countryLanguage) {
  entityManager.persist(countryLanguage);
  return countryLanguage;
 }

 @Override
 public CountryLanguage update(CountryLanguage countryLanguage) {
  return entityManager.merge(countryLanguage);
}
@Override public CountryLanguage remove(CountryLanguagePK key) { CountryLanguage countryLanguage= entityManager.find(CountryLanguage.class, key); if (countryLanguage!=null) entityManager.remove(countryLanguage); return countryLanguage; } @Override public CountryLanguage find(CountryLanguagePK key) { return entityManager.find(CountryLanguage.class, key); } @Override public Collection<CountryLanguage> findAll() { List<CountryLanguage> languages = entityManager .createQuery("select cl from CountryLanguage cl", CountryLanguage.class) .getResultList(); return languages; } @Override public CountryLanguage find(CountryLanguagePK key, String graphName) { EntityGraph<?> eg= entityManager.getEntityGraph(graphName); Map<String,Object> props= new HashMap<>(); props.put("javax.persistence.fetchgraph", eg); return entityManager.find(CountryLanguage.class, key, props); } }