Yeni Başlayanlar için Spring Boot MVC ve PostgreSQL ile Proje Oluşturma

Image for post
Image for post

Herkese Merhaba, öncelikle Spring Boot nedir, ne işe yaramaktadır bununla başlayalım. Kendi sitesindeki tanımına göre Spring Boot, bize uygulama oluşturmanın hızlı bir yolunu sunar. Sınıf yolumuza ve yapılandırdığımız çekirdeklere bakar, neyi kaçırdığımız hakkında makul varsayımlar yapar ve bu öğeleri ekler. Spring Boot ile iş özelliklerine daha çok, altyapıya daha az odaklanabilmekteyiz. Spring Boot, standalone uygulamalar için gerekli tüm .jar’ları kendi içinde barındırır ve platformdan bağımsız uygulamayı hızlı bir şekilde ayağa kaldırabilir.

MVC (Model-View-Controller), yazdığımız uygulamanın iş mantığı (business logic) ile kullanıcı arayüzünü birbirinden ayrıştıran, uygulamanın farklı amaçlara hizmet eden kısımlarının birbirine girmesini engelleyen yazılım mimarisidir. Basitçe açıklayacak olursak; Model, bir program tarafından kullanılan verilerdir. Bu bir veritabanı, dosya veya bir video oyunundaki bir simge veya karakter gibi basit bir nesne olabilir. View, bir uygulama içindeki nesneleri görüntüleme aracıdır. Kullanıcının görebileceği her şeyi içerir. Controller, hem modelleri hem de görünümleri günceller. Girişi kabul eder ve ilgili güncellemeyi gerçekleştirir.

Ben Spring Boot ile basic bir HelpDesk Projesi oluşturdum. Bu yazımda size projemin önemli kısımlarını anlatacağım.

Projenin bütün kaynak kodlarına buradan ulaşabilirsiniz: https://github.com/minnela/HelpDeskSystem

Spring Boot’un mantığını daha iyi kavramamız açısından bu projede back-end tarafına yoğunlaşacağız. Projemin temel mantığından biraz bahsedecek olursam, yardım masası sistemine kayıt olan kullanıcılar sisteme giriş yaparak şikayetlerini şikayet formu (issue form) doldurarak oluştururlar. Sistemin adminleri bütün herkesin şikayet ve sorunlarını görebilmekte ve kişilerin sorunlarına yorum yazabilmekteler. Projede kullandığımız en önemli özelliklerden ikisi sizin de anlayacağınız üzerine Authorization ve Authentication.

Projemize başlamadan önce gerekliliklerimiz:
PostgreSQL 12,
JDK (en az 11(2),
Maven kullanabilmek için bir IDE ( Ben IntelliJ Idea kullandım.)

Kendi işletim sisteminize uygun PostgreSQL’i burdan indirebilirsiniz: https://www.postgresql.org/download/

Bütün gerekliliklerimizi kurduktan sonra yeni bir proje oluşturmaya başlayabiliriz. IntelliJ Idea’ da File>New>Project > Maven seçerek yeni bir Maven projesi oluşturuyoruz.

Projemizin structure’ı aşağıdaki gibi olacak:

Image for post
Image for post
Project Structure

pom.xml dosyasını şu şekilde güncelliyoruz: (thymeleaf dependency, view katmanında kullanacağımız bir template engine’dir)

Spring Boot ile İlk Proje

Database’imizi kurmadan önce basit bir Home sayfası yapalım. Controller package’i altında HomeController adlı bir class oluşturalım.

HomeController:

HomeController classımızı Spring Boot’un @Controller anotasyonu ile belirtiyoruz. Burada @RequestMapping anotasyonu ile Home anasayfamıza erişim sağlamak için gerekli url’leri belirliyoruz. Spring Boot default olarak localhost:8080 port adresinde çalışmaktadır. Bu adresi ben localhost:1236 olarak değiştirdim. Siz de başka bir port adresi belirleyebilirsiniz. Adresi değiştirmek için application.properties dosyasına girerek server.port= 1236 yazmamız yeterli olacaktır. Home Controller’dan anlayacağımız üzere localhost:1236/ veya localhost:1236/home adresleri bizi anasayfaya götürecek. Burada return”home” olarak belirttiğimiz ifade bizim home.html sayfamız olacaktır. Anasayfaya istek attığımızda bizi home.html sayfasına yönlendirecek. Bunun için resources>templates>home.html adlı bir dosya oluşturalım.

Ben biraz da görsellik olması açısından anasayfaya arka plan resmi ekledim. Ayrıyeten html sayfalarımda bir bootstrap teması kullandım. Ama daha önceden de dediğim gibi front-end tarafından ziyade bu projede back-end tarafına odaklanıyoruz.

home.html:

<h1>Welcome Help Desk System!</h1><script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>

Ve şimdi geldik Main classımızı oluşturmaya. Proje dizinimizde Application isimli bir class oluşturup üzerine @SpringBootApplication anotasyonunu koyuyoruz.

Application class:

İşte şimdi projemiz çalıştırmaya hazır! localhost:1236/' ya istek yaparak anasayfamızı görebiliriz.

Issue Ekleme ve Listeleme

Şimdi Issue entity’mizi oluşturalım. Issue, kullanıcıların yardım masasına gireceği bir isteği, sorunu veya yardım isteğini temsil etmektedir. Bunun için domain dizini altında Issue classımızı oluşturalım:

Model

Issue:

Issue classımızın başına @Entity anotasyonu ekleyerek bu classın bir entity class’ı olduğunu belirtiyoruz. @Column anotasyonu ile issue tablomuzun columnlarını belirliyoruz. Class’a getter setter ve constructorları da ekledikten sonra Issue class’ımız hazır hale gelmektedir.

Şimdi sıra geldi kullanıcıların sisteme ekleyeceği issue’lar için bir issue form , controller ve veritabanı yaratmaya. Projemize PostgreSQL veritabanını entegre etmek için application.properties dosyasına şunları ekleyelim:

application.properties:

Daha sonra resources dizini altında data.sql adlı bir sql dosyası oluşturup issue tablomuzu yaratacağız. data.sql dosyasını oluşturduğunuzda IntelliJ idea sisteminize bir sql entegre etmenizi isteyecek ve bir uyarı verecektir. Çıkan uyarıda Configure data source seçeneğine tıklayarak IntelliJ Idea içine de PostgreSQL kuruyoruz ve böylece database’imizi IDE içine entegre ediyoruz. Entegre edilen database’imizde manuel olarak users ve issue tablolarını oluşturuyoruz. Burada altını çizmek istediğim bir konu, postgreSQL’in default olarak “user” adında bir tablo bulundurmasından dolayı bizim yazacağımız column’larda hata almamak için ben tablomu users ismiyle oluşturdum.

Database’imizi oluşturduktan sonra sıra geldi Model katmanımızı oluşturmaya. Verilerin eklenip, tutulması için repository dizini altında IssueRepository adlı interface’imizi oluşturalım.

IssueRepository:

IssueRepository interface’ini implemente eden yine repository dizininin altında bulunan IssueRepositoryImpl class’ını oluşturalım. Implementasyon classımızda database’e issue ekleme, userlara ait issueları listeleme gibi özellikler yazacağız. Database’deki issuelara erişebilmek için bir mapper’a ihtiyacımız olacak. Bunun için önce mapper dizini altında IssueRowMapper class’ını oluşturuyoruz.

IssueRowMapper:

IssueRowMapper classını, issueRepositoryImpl classında yazacağımız sql stringleri ile birlikte kullanarak issue tablosundaki istediğimiz column’ları getirmiş olacağız. Şimdi IssueRepositoryImpl classına bir bakalım:

IssueRepositoryImpl:

Implementasyon classımıza @Repository anotasyonunu koyuyoruz. Repository, database’de tuttuğumuz dataya erişim objemizdir. Database’de veri işlemleri yapabilmek için Spring Boot’un sql template özelliği olan jdbc template’ini ve DataSource’u oluşturuyoruz. addIssue metodunda jdbc template’in insert özelliğini kullanarak bir map oluşturuyoruz ve database’imize eklenecek olan issue’ları kaydediyoruz. Burada altını çizmek istediğim nokta, kaydedilen issue’ya ait kullanıcı username’i o an login olan username olarak belirlenmektedir. Yani issue tablosunda, issue sahiplerinin de username’leri barındırılmaktadır. Şimdi uygulamaya girip, bir issue formu doldurarak bunu database’e kaydetmek isteyelim.

Service

Öncelikle controller ve model arasında bir köprü görevi gören service katmanımızı oluşturalım. Service, aslında Controller’da olabilecek business logic’i encapsulate etmek için yarattığımız, Controller ve Model arasında duran bir katmandır. Bunun için service dizini altında bir issueService interface’i ve onu implemente eden issueServiceImpl classını oluşturuyoruz:

IssueService ve IssueServiceImpl:

@Service
public class IssueServiceImpl implements IssueService {

@Resource
IssueRepository issueRepository;

@Autowired
public IssueServiceImpl(IssueRepository issueRepository) {
this.issueRepository = issueRepository;
}

@Override
public void addIssue(Issue issue) {
issueRepository.addIssue(issue);
}

View

Şimdi sıra geldi addIssue html’ini oluşturmaya. Burada issue eklemek için bir formumuz olacak. Aşağıya sadece formu paylaşıyorum, addIssue.html sayfasının bütününe proje kaynak kodundan ulaşabilirsiniz. Formda kullandığımız thymeleafin özelliği th:action ile localhost:1236/issues sayfasına bir post isteği yaptığımızı belirtiyoruz. th:object ile Issue classından türeyen issue nesnesini kullandığımızı belirtiyoruz. th:field ile issue nesnesinin hangi özelliğini kapsadığımızı gösteriyoruz.

addIssue.html:

Controller

Şimdi geldik controller classını oluşturmaya. Controller dizini altında bir IssueController yaratarak issue ekleme işlemimizi tamamlayalım:

IssueController:

Burada issues/add adresi ile addIssue sayfasına erişiyoruz. Karşımıza sisteme istekte bulunabilmek için bir issue formu çıkıyor. Issue formunu doldurduktan sonra bu bilgileri issue sayfasına post ediyoruz. Post fonksiyonumuz issueService aracılığıyla issue’ları database’e kayıt ediyor ve bizi issues sayfasına yönlendiriyor. Issues sayfası ise bütün issuelarımızın tablo olarak gösterildiği ve sıralandığı sayfa. getIssuesPage() methodunda ModelAndView objesi sırayla (“viewName”, “modelName”, getUserIssue()) değerleri almıştır. issues.html sayfasında gördüğümüz th:each kodu, getIssuesPage() methodumuzdaki issues olarak gönderdiğimiz model ismini kullanır. Böylece bütün issueları tarayarak teker teker columnları sayfaya yazdırabilmekteyiz :

issues.html:

User Kayıt

Şimdi sisteme user register işlemi ekleyelim. Bunun için öncelikle Users adlı class’ımızı oluşturalım. Spring Boot’un default olarak User isimli class’ı olduğu için classların birbirine karışmaması adına ben classımı Users olarak oluşturdum:

Model

Users:

Yine getter,setter methodlarımızı ve constructor’ımızı ekliyoruz. Ardından aynı issue için yaptığımız gibi UserRowMapper classını, UserRepository interface’ini ve UserRepositoryImpl classını oluşturuyoruz:

UserRowMapper:

UserRepository ve UserRepositoryImpl:

Burada user kaydı yaparken, her kayıt olan user’ın rol atamasını “user” olarak belirledik. Sistemin adminlerini veritabanımıza manuel olarak gireceğiz.

@Repository
public class UserRepositoryImpl implements UserRepository{
private JdbcTemplate jdbc;
private EncryptedPasswordUtils encryptedPasswordUtils;

@Autowired
public UserRepositoryImpl(DataSource dataSource) {
this.jdbc = new JdbcTemplate(dataSource);
}

@Override
public void addUser(Users user) {

user.setUserRole("user");
String encryptedPassword= encryptedPasswordUtils.encrytePassword(user.getPassword());

user.setPassword(encryptedPassword);

SimpleJdbcInsert insertUser = new SimpleJdbcInsert(jdbc).withSchemaName("public").withTableName("users").usingGeneratedKeyColumns("userid");
Map<String, Object> parameters = new HashMap<>(2);
parameters.put("userid", user.getId());
parameters.put("username", user.getName());
parameters.put("usersurname", user.getSurname());
parameters.put("userpassword", user.getPassword());
parameters.put("useremail", user.getUsername());
parameters.put("userrolee", user.getUserRole());
Number id = insertUser.executeAndReturnKey(parameters);
user.setId(id.longValue());
insertUser.execute(parameters);

}

@Override
public List<Users> getUsers() {
return jdbc.query("select * from users",new UserRowMapper());
}

Ayrıca, user kayıt olurken belirlediği passwordu de güvenliği arttırmak için veritabanına encryption yaparak, yani şifreleyerek kaydediyoruz. Encrption yapmak için utils dizininin altında EncryptedPasswordUtils adlı bir class oluşturup şu kodları ekliyoruz:

Service

Ardından controller ve model arasında iletişim kurmak için service dizini altında UserService interface’ini ve UserServiceImpl classını oluşturalım:

UserService ve UserServiceImpl:

@Service
public class UserServiceImpl implements UserService, UserDetailsService {

@Resource
UserRepository userRepository;
IssueRepositoryImpl issueRepository;


@Autowired
public UserServiceImpl(UserRepository userRepository, IssueRepositoryImpl issueRepository) {
this.userRepository = userRepository;
this.issueRepository=issueRepository;
}

@Override
public void addUser(Users user) {
userRepository.addUser(user);
}

@Override
public List<Users> getUsers() {
return userRepository.getUsers();
}

View

Yine aynı addIssue formunda yaptığımız gibi resources dizini altında bir register formu oluşturalım.

register.html:

Controller

Gördüğümüz gibi bu sefer formda user objesini aldık. Artık register işlemi için UserController yaratmaya hazırız:

UserController:

Yine issue kayıt etme mantığına benzer şekilde register sayfasına erişmek için bir GET isteği, kayıt olmak için de doldurduğumuz formla birlikte bir POST isteği yaptık.

LOGIN İŞLEMİ — AUTHENTICATION & AUTHORIZATION

Veritabanımıza kayıtlı kullanıcılarımızı oluşturduk. Şimdi ise sırada login işlemi var. Login olmak isteyen kullanıcı veritabanında kayıtlı ise authentication işlemi true dönecek ve sisteme giriş sağlanacaktır.

Öncelikle, pom dosyamıza aşağıdaki dependencyleri ekleyelim:

İlk dependencye, login olmak isteyen kullanıcıları doğrulamak için, ikincisine ise thymeleaf’in bir takım login özelliklerini kullanabilmek için ihtiyacımız var.

Öncelikle config dizininin altında WebSecurityConfig adında bir class oluşturuyoruz. Burada password decryption ( şifre çözme) , kullanıcı doğrulama işlemleri yapılmaktadır. Ayrıca authorization işlemi yapıyoruz, yani hangi sayfaya, hangi yetkiye sahip kullanıcıların erişebileceğini belirliyoruz.

Ardından UserServiceImpl classımızda UserServiceDetails interface’ini implemente ediyoruz. UserDetailsService, Spring Security’nin user girişini (user password, user yetkileri gibi ) loadUserByUsername methoduyla kontrol ettiği bir interfacedir.

grantList ile kullanıcının yetkilerini bir listede tutuyoruz ve userDetails nesnesine veriyoruz.

Ayrıca o an login olan kullanıcıyı sistemde tutabilmek için ve ekrana kullanıcıya özel issue sayfası getirebilmek için issueRepository’de CurrentLoginedUser fonksiyonu oluşturdum ve bunu loadUserByUsername işleminde o an login olan kullanıcı ismiyle set ettim.

Login Controller

Şimdi ise Controller dizini altında LoginController class’ı oluşturalım. Spring Boot’da login işlemi yaparken post methoduna ihtiyacımız yoktur. Login için controllerda sadece get methodu yazmamız yeterlidir. Bu da Spring Boot’un bize kolaylık sağladığı özelliklerinden biri.

@PreAuthorize anotasyonuyla, getLoginPage methodunun sadece giriş yapmamış kullanıcılar için getirileceğini belirtiyoruz.

View

Login sayfamız için loginPage.html oluşturuyoruz:

Kullanıcıya Özel Issue Sayfası Listeleme

Her kullanıcı sisteme login olduktan sonra sorunlarını help deske yazabilmek için Add Issue sayfasına gitmekte ve istek yaptığı issue’ların listesini görebilmektedir. Ayrıca her issue’nun yanında Show Solution butonu bulunmakta ve böylece sitem adminlerinin yazmış olduğu çözümleri de görebilmektedir. Sadece tek bir issue viewımız var, fakat biz her kullanıcının sadece kendi eklediği issue’ları görebilmesini istiyoruz. ADMIN yetkisi olan kullanıcının ise bütün issue’ları görebilmesini istiyoruz. Öncelikle issues.html sayfamızın form kısmına tekrar bakalım:

issues.html

Şimdi IssueRepositoryImpl classına giderek getUserIssues() adlı methodu oluşturuyoruz (Önce IssueRepository interface’inde methodu oluşturup, burada implemente etmeyi unutmuyoruz):

getUserIssues() methodunda, o an login olmuş olan user’ın username’i çağrılarak kendisine ait olan issue döndürülmektedir.

Şimdi ise IssueController’da da bunu belirtelim:

Evet görüldüğü üzere artık user’a özel bir issue sayfası görünümü yarattık. Şimdi ise localhost:1236/admin url’i ile oluşturacağımız admin sayfasına bağlanalım ve sistemde oluşturulan bütün issue’ları görüp onlara comment ekleyebilme özelliğini oluşturalım.

Bunun için önce IssueRepositoryImpl classına veritabanındaki bütün issueları listeleyen bir method yazalım:

Şimdi ise IssueController’da admin sayfası için bir fonksiyon oluşturalım:

Viewımızı oluşturalım: AdminPage.html:

Şuanda admin, veritabanına kayıtlı bütün issueları görebilmektedir. Her bir issue’nun sonunda write a comment butonu ekledik. Böylece admin gönderilen issue’lara çözüm yazacak ve kullanıcının ekranına gönderecektir. Write a comment butonuna basan admin için requestSolutionPage sayfası açılır.

View — requestSolutionPage.html

IssueController’da- getRequestSolutionPage fonksiyonu:

getRequestSolutionPage View’dan da anlaşılacağı üzere admin gönderilen issue için bir comment yazar ve submit butonuna basar. Submit edilen comment tıpkı issue eklerken yaptığımız gibi bir post methoduyla yeni bir sayfaya gönderilir, veritabanındaki issue tablosunda issueComment sütununa eklenir ve çözüme kavuşturulan issue’ların hepsi gösterilir.

Kullanıcıya özel çözülmüş issue sayfası için issueRepositoryImpl classında şu methodu yazarız:

Bu method, admin tarafından çözüme kavuşturulan her issue’nun tablosunu güncellemektedir.

Kullanıcı kendi issue’sunun çözümünü görmek için show solution butonuna tıklar ve çözüme kavuşmuş ve böylece update edilen issue sayfası önüne gelmektedir. Bunun için showSolution sayfasına admin tarafından yapılan bir post isteği ve user tarafından yapılan bir de get isteği bulunmaktadır.

ShowSolution- Controller (IssueController classı içinde)

View- showSolution.html:

Ardından yine aynı mantıkla adminin de bütün issueların çözümlerini görebilmesi için bir showSolutionAdmin sayfası ekledim.

Böylece sizlere Spring Boot projemin en önemli hatlarını anlatmış bulunmaktayım. Daha ayrıntılı bilgi için siz de projenin kaynak kodlarını inceleyebilirsiniz.

Test Driven Development

Son olarak TDD olarak adlandırdığımız Test Driven Development yöntemi, günümüzde kod yazmada çok önemli bir yer haline gelmiştir. Spring Boot projemiz için sizlere örnek bir test classı da ekleyeceğim. Testimizde UserService’i test etmek amacıyla geçici Mock nesneleri oluşturuyoruz (UserRepository, IssueRepository). Ardından setUp kısmında bir user oluşturup set ediyoruz. Sonunda findUserByEmail test methoduyla userService’in getUserByUsername() methodunu test ediyoruz. Ve testimizi tamamlıyoruz.

İlk blog yazımı okuduğunuz için teşekkür ederim.

Bol kodlamalı günler :)

https://github.com/minnela/HelpDeskSystem

Written by

Computer Engineer & Industrial Engineer. Passionate about software. Always eager to learn.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store