29 Nisan 2018 Pazar

MapStruct ile Entity Dto Dönüşümü

Merhabalar,
REST servislerimizde domain entity (hibernate) nesnelerini direk dönmek güvenlik açığı oluşturabildiği gibi, bidirectional ilişkilerde JSON'a serialize etmeye çalışırken sonsuz döngüye girip hata verme tehlikesi bulunmaktadır. Bu sorunu entity nesnelerimizi basit data transfer object (DTO) nesnelerine dönüştürüp dönerek aşabiliriz. Peki her defasında onlarca defa getX() setX() metodlarını çağırmak yorucu olmayacak mı? Bunun için de MapStruct (java bean mapping) kütüphanesini kullanabiliriz. Tanımlayacağımız basit mapper nesneler ile get set metod çağırım yükünden kurtulabiliriz.



Bu yazımızda Spring Boot projesi içinde H2 veritabanı ile, MapStruct kullanımını göreceğiz. Veri modeli olarak klasik Pet Clinic yapısını kullanacağız. Evcil hayvan, evcil hayvan sahipleri, veteriner, hayvan tipleri gibi veri yapıları ile Hibernate Mapping örneklerini de görmüş olacağız.

Tüm örnek proje kodunu Github'da şuradan indirebilirsiniz.

Veri modelimiz aşağıdaki gibidir. Owner ve Pet arasında karşılıklı (bidirectional) ilişki bulunmaktadır. Bu nedenle Owner nesnesini JSON olarak dönmek istediğimizde, içindeki Pet'leri JSON'a çevirirken, Pet içindeki Owner nesnesini de yine JSON'a çevirmeye çalışacağı için sonsuz döngüye girerek başarısız olmaktadır.

Projeye yapısına genel olarak bir bakalım.

model paketi altında domain entity nesnelerimiz bulunuyor. Dto paketi içinde test için kullanacağımız entity nesnelerinin dto nesneleri bulunuyor. mapper paketi içinde MapStruct mapper interface sınıfları bulunuyor. repository paketinde Spring Data JPA repository sınıflarımız bulunuyor. service paketi içinde ise repository nesnelerini kullanan servis sınıflarımız bulunuyor.

application.properties dosyası içeriği aşağıdaki gibidir:

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
server.port = 8080
spring.datasource.url = jdbc:h2:file:~/h2/app_db;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username = sa
spring.datasource.password =
spring.datasource.driverClassName = org.h2.Driver
spring.jpa.hibernate.ddl-auto = update


Projemiz Maven projesi olduğundan bağımlılıkları incelemek için pom.xml içerisinde bağımlılıkları tanımlıyoruz.
        <dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-jdk8</artifactId>   

                <version>${org.mapstruct.version}</version>
        </dependency>


MapStruct, derleme aşamasında tanımlanan mapper interface veya abstract sınıflarım implementasyonlarını otomatik olarak oluşturmaktadır. Bunun için maven compile plugin içerisine bir mapstruct-processor tanımı yapmak gerekmektedir. 

<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
                <source>1.8</source> <!-- or higher, depending on your project -->
                <target>1.8</target> <!-- or higher, depending on your project -->
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>

            </configuration>
        </plugin>
        </plugins>
    </build>


Domain ve Dto sınıflarımıza bir bakalım:




Mapper interfacelerimize bir bakalım. Dikkat edilirse Owner entity'si içinde Pet entity'si var. Bu nedenle Pet için de mapper tanımlamalı ve OwnerMapper sınıfını bu maperdan haberdar etmeliyiz:
Bunun için @Mapper anotasyonu içindeki uses alanını kullanıyoruz. Ayrıca Spring'in dependency injection kabiliyetinden faydalanıp mapper'ları bean olarak kullanabilmek için @Mapper anotasyonu içinde componentModel = "spring" tanımını yapıyoruz.

OwnerMapper 

PetMapper


PetTypeMapper

Maven ile derlendiğinde target klasörü altına otomatik olarak üretilmiş mapper impl dosyaları görünür olacaktır. 
mvn clean compile yapılarak derlenmelidir.


 Otomatik üretilmiş OwnerMapperImpl içeriği aşağıdaki gibidir:


Şimdi OwnerMapper'ı kullandığımız OwnerService koduna bakalım.


Repository'den dönen entity'lerin direk dönüldüğü ve mapper ile Dto'ya çevrilip dönüldüğü iki listeleme metodu tanımladık.

Test Vakti

Gelelim projeyi test etmeye. OwnerService sınıfında örnek Owner kaydedecek metod tanımladık. OwnerController içinde /owner/add ile istek gelirse bu metod çağrılacak. Listelemek için de controller içinde iki metod tanımladık. /owner/list ve /owner/listCorrect. /owner/list metodu normal Owner entity listesi dönmektedir. Bu sayede sonsuz döngüye girme hatasını görebileceğiz. /owner/listCorrect ile de OwnerDto listesi dönecek ve başarılı bir şekilde ownerların JSON dökümünü görebileceğiz.


Spring Boot projemizi çalıştıralım ve http://localhost:8080/owner/add adresine gidelim. Add metodu da aslında OwnerDto listesi döndüğünden JSON serialize hatasıyla karşılaşmadığımızı görebiliriz. 
Veritabanına bakmak için http://localhost:8080/h2-console linkine gidip jdbc adresi alanına application.properties kısmında yazan jdbc adresini yazarak giriş yapabiliriz.





http://localhost:8080/owner/list adresine giderek hatayı üretelim.


Konsola düşen Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException hata loglarına bakalım:




 http://localhost:8080/owner/listCorrect adresine giderek sorunsuz dönen cevaba bakalım:

MapStruct kullanırken unutmaması gereken ufak bir not: Mapper sınıfların implementasyonları maven derleme sırasında üretildiği için, kullandığınız IDE'nin otomatik build ettiği sırada üretmeyecektir. Bu sebeple uygulama ayağa kalkarken bean bulunamadı hatası alabilirsiniz. Bu nedenle impl dosyalarının üretildiğinden emin olup uygulamayı öyle başlatınız.

Tüm örnek proje kodunu Github'da şuradan indirebilirsiniz.




Hiç yorum yok:

Yorum Gönder

SSHJ ile Raspberry'de Uzaktan Python Çalıştırmak

Merhabalar, Bu yazımızda, Java ile uzaktaki bir makineye programatik olarak SSH ile bağlanıp Python kod çalıştırmayı göreceğiz. Örnek olarak...