Spring Boot, Spring Framework, Hibernate ORM ve Thymeleaf ile bir blog sitesi oluşturmak

Blog yazmaya ilk başladığım zamanlarda bu şekilde bir blog sitesinin spring boot MVC ile nasıl yapılabileceğini anlatan bir yazı yazmıştım. O yazıya buradan ulaşabilirsiniz fakat o zamanın teknolojileri ve benim blog yazım biraz eskide kaldı. Bu yüzden bu yazıda spring boot MVC ve Thymeleaf ile bu blog sitesi gibi bir sitenin iskeletini oluşturacağım. Yeni ve yeniden :D

Yazının içeriği eski yazıdakinin neredeyse birebir aynısı olacak fakat üzerine biraz eklemeler de yapacağım. Bu yazıda anlattığım sırayı izleyerek kendi blog siteniz için başlangıç adımlarını atmış olursunuz. Sonrasında çeşitli sayfalar, CSS 'ler veya özellikler ekleyerek geliştirmeye devam edebilirsiniz. Adım adım ilerleyelim.

#1. Proje çatısı (Spring Boot)

Günümüzde çok yaygın kullanılan spring framework yazılım geliştirme metodolojisini kolaştırmakta ve spring boot altyapısı ile hızlandırmaktayız. İlk aşamada bize lazım olan bir spring boot projesidir. Bunun için Spring Initilizr adresine gidip aşağıdaki resimdeki gibi bir template oluşturabilirsiniz.

Bu ekranda sol taraftakiler tamamlayıcı teknolojilerden birisi olan maven için gerekli. Bu bilgiler sayesinde maven projenize bir hiyerarşik yapı verecek ve paketleyecek. Proje çıktısı jar olabilir çünkü spring boot aslında içinde bir main class olan uygulamadır.

#2. Bağımlılıklar (Dependency 'ler)

İlk sıradaki dependency devtools sayesinde projeniz güncellediğinizde hemen derlenir ve kendi içindeki sunucuya otomatik deploy edilir. Zaten spring boot ile ekstra bir tomcat gibi bir sunucu ihtiyacımız yoktur. Run as spring boot application ile projeniz ayağa kalkar ve localhost://8080 'de çalışmaya ve otomatik güncellemelere başlar.

Sonraki Spring Web az önce söylediğim web uygulaması olarak çalışmasını sağlıyor. İçerisinde tomcat otomatik olarak geliyor ve devtools ile buraya deploy ediliyor. Run on server yerine run as spring boot aplication ile ayağa kalkıyor. Dilerseniz main metodunuzu da ayağa kaldırabilirsiniz. Spring boot bu şekilde sizi tembelleştirerek işleri halleder :)

Thymeleaf ise MVC dizaynı ile kullanabileceğiniz güzel bir html etiket kütüphanesi yani tag library 'dir. Önyüze aktarılan bilgileri çeşitli yapılarda html verisine dönüştürüyorsunuz, mesela th:each gibi bir komut ile bir elemanı for döngüsü gibi çoğaltabiliyorsunuz. Spring framework ile iç içe çok verimli çalışabiliyorlar. Detaylar burada.

Bir sonraki ise Spring Security ile erişim kontrolü, şifre koruması veya cross site script koruması gibi işlevleri olabiliyor. Spring securty 'yi sitenizin admin sayfası için kullanırsınız muhtemelen diye düşünerek ekledim ve basitçe configure ettim.

Spring data JPA ise veritabanı işlemlerinizi native sql ifadeleri yazmadan gerçekleştirebilmenizi sağlıyor. Aynı zamanda veri modellerinizi (entity) kodunuz içerisinde yazdığınız sınıflarla tanımlamanızı ve güncellemenizi de sağlayarak geliştirme sürecini anlaşılır ve hızlı hale getirebiliyor.

PostgreSQL Driver yerine farklı veritabanları (MySql, Oracle, MSSQL gibi) kullanılabilir. Bu dependency örnek olması açısından burada. Siz bir veritabanına bağlanmak için bu teknolojilerden herhangi birini seçebilirsiniz. Bu dependency ile spring data JPA hangi veritabanına bağlanacağını biliyor.

#3. Backend tamam, arayüz nerede?

Arayüz için thymeleaf kütüphanesinin özelliklerinden yararlanacağız fakat profesyonel css javascript arayüzleri yazamıyorsanız veya angular react gibi kütüphaneler konusunda bilginiz yoksa internette hazır çok sayıda html css template bulunuyor. Tabi bir link ile tasarımı yapan siteye atıfta bulunarak kullanmak kaydı ile. Ben ColorLib ile template bulup üzerine kod ile geliştirme yapmayı tercih ettim. Bu sitede çoğunlukla wordpress temaları olsa da html + bootsrap (yani css) ile hazırlanmış şablonları da var. Bu örnekte arayüzlere özen göstermedim ve berbat görünüyorlar fakat zaten o kısmı siz hallediyor olacaksınız.

#4. Proje geliştirmeye hazır

Önyüz için bir tema bulup veya hazırlayıp projeye dahil ettikten sonra (templates altındaki index.html sayfası mesela) projenin eclipse 'de aşağıdakine benzer bir yapıda görünmesi lazım. Eclipse 'de Import Existing Maven Project işlemi ile import edebilirsiniz.

Eclipse için marketten Spring Tools Suite eklentisini de kurmayı unutmamalısınız.

Resources altındaki static klasörü içinde resim, css, javascript dosyaları yer almalıdır. Thymeleaf ve spring boot ikilisi bu klasör yapısı ile çalışır. Templates klasörü ise web sayfalarınız olacaktır. Index.html ve entry.html burada mesela. application.properties uygulamanın kullanacağı esas bilgileri içerecektir. Veritabanı bağlantıları loglama seviyesi mail sunucusu ...vs gibi. En alttaki pom.xml ise bağımlılıkları ve derleme işlemin yöneten xml konfigürasyonudur.

Bu proje şu anda run as -> spring boot application şeklinde çalıştırılmaya hazırdır teknik olarak fakat spring boot ayağa kalkarken postgresql driver yüzünden veritabanı bağlantı bilgileri isteyecektir. Ayrıca spring security ve resim cache 'lemek için de bonus özellikler ekledim. Örnek olarak postgresql için application.properties dosyasında aşağıdaki gibi bilgiler gerekecektir:

		
server.shutdown=graceful
spring.datasource.username = postgres
spring.datasource.url = jdbc:postgresql://localhost:5432/blog
spring.datasource.password = somepassword
spring.jpa.hibernate.ddl-auto = update
spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true
spring.jpa.properties.hibernate.current_session_context_class = org.springframework.orm.hibernate5.SpringSessionContext
spring.security.user.name = myadmin
spring.security.user.password = myadminpassword
spring.web.resources.cache.cachecontrol.max-age = 30d
spring.web.resources.cache.cachecontrol.no-cache = false
spring.web.resources.cache.cachecontrol.no-store = false
		
	

Bu şekilde kullandığınız veritabanı bilgilerini yazdıktan sonra http://localhost:8080/index.html adresine gittiğinizde karşınıza kapı gibi spring security çıkar ve kullanıcı adı şifre ister. Properties 'deki gibi spring.security.user.name ve password ile geçebilirsiniz. Bu aşamada 404 gibi bir hata alabilirsiniz ama sorun değil.

Security 'yi sadece admin sayfası gibi alanlarda kullanırsınız muhtemelen. Bu şekilde bir ayarı yapabilmek veya bütün sayfalara izin verebilmek için aşağıdaki gibi bir sınıfı projeye dahil etmelisiniz. /admin sayfası kullanıcı adı şifre isteyecek ve geri kalan sayfalar direkt olarak açılacaktır.

			
@Configuration
public class SeConfig
{
	@Bean
	SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception
	{
		http.authorizeHttpRequests(custom -> custom.requestMatchers("/admin").authenticated());
		http.authorizeHttpRequests(custom -> custom.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll());
		http.authorizeHttpRequests(custom -> custom.anyRequest().permitAll());
		return http.build();
	}
}
			
		

@Configuration sayesinde spring framework bu sınıfı konfigürasyon sınıfı olarak görür ve örneğin spring security 'yi ayağa kaldırırken bu annotation ile işaretlenmiş sınıflar arar. SecurityFilterChain bean 'i güvenlik katmanlarında kullanılır. Bu şekilde security ayarını basic seviyede yapmış oluyoruz ve anasayfamızı yani index sayfasını hazırlamaya geçiyoruz. Veritabanında tutacağınız hibernate entity 'lerini yani nesnelerini (tabloları) tanımlamak gerekiyor öncelikle. Çünkü açılan sayfada bir sorgu yapıp ekrana liste getireceğiz. Bunun için bir örnek aşağıdaki gibi olabilir:

			
@Entity
@Table(name = "ENTRY")
public class Entry
{
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", nullable = false)
	private int id;

	@Column(name = "TITLE", nullable = false, length = 1500)
	private String title;

	@Column(name = "IMG_URL", nullable = false, length = 200)
	private String imgurl;

	@Column(name = "SUMMARY", nullable = false, length = 2000)
	private String summary;

	@ManyToOne
	@JoinColumn(name = "CAT", referencedColumnName = "ID", nullable = false)
	private Category category;

	@ManyToMany
	@JoinTable(name = "EntryTags", joinColumns = @JoinColumn(name = "entryId"), inverseJoinColumns = @JoinColumn(name = "tagId"))
	private List<Tag> tags;

	@Column(name = "PUB_DATE", nullable = false)
	private Date publishDate = new Date();
	
	// getters setters constructors...
}
			
		

@Entity bir entity yani tablo olduğunu belirtiyor. @Table ile veritabanındaki tablo ismini best practice olması adına belirtiyoruz. Buradaki join 'lerle ilgili söylenebilecek çok şey var ben örnek olarak n-1 Category ve n-n Tag sınıflarını da yazdım. Siz kendi sitenizde kendi veritabanı yapınızı geliştireceğiniz için detaylandırmanın mantığı olmayacak. Ama buna benzer ilişkiler mutlaka kuracaksınız, örneğin yazı kategorisi ve etitekleri mutlaka olacaktır sanırım.

Sonrasında bu sınıfın kullandığı Category ve Tag sınıflarını da tablo olarak tasarladık. Onlar da entity şeklinde aşağıdaki gibi.

			
@Entity
@Table(name = "Category")
public class Category
{
	@Id
	@Column(name = "ID", nullable = false)
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;

	@Column(name = "NAME", nullable = false, length = 100)
	private String name;
	
	// getters setters constructors...
}

@Entity
@Table(name = "TAG")
public class Tag
{
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", nullable = false)
	private int id;

	@Column(name = "NAME", nullable = false, length = 100)
	private String name;

	@ManyToMany(mappedBy = "tags")
	@OrderBy(value = "id desc")
	private List<Entry> entries;
	
	// getters setters constructors...
}
			
		

Bu nesneleri yöneten veritabanı erişim sınıfı yazacaksınız tabi CRUD işlemleri için. İlk yazınızı yani post 'unuzu veritabanına manuel olarak eklyebilirsiniz. Öncelikle uygulamayı şu anda bir kere ayağa kaldırıp veritabanının oluşmasını sağlayınız. Önce "blog" isimli veritabanının oluşturmanız gerekecektir. Sonrasında aşağıdaki resimdeki datalardan kopya çekebilirsiniz.

Data 'yı oluşturduktan sonra bu dataya erişecek olan basit bir entry repository 'si işimizi görecektir bu demo için.

		
@Repository
public interface EntryRepository extends JpaRepository<Entry, Integer>, JpaSpecificationExecutor<Entry>
{
	// bu kadar :)
}
		
	

@Repository de @Component yada @Service gibi bir spring anotasyonudur. Veritabanına erişim yapan sınıflar için best practice olarak kullanılmaktadır. JpaRepository ile Entry sınıfı üzerinde çok kullanılan işlemleri hazır yapabiliyoruz.

Sonrasında /index.html ile browser 'a gitmeye çalıştığınızda index sayfasını bulamadım hatası alacaksınız. Burada bir yönlendirme yapılması gerekiyor ve bunun için spring framework 'ün controller yapısını kullanacaksınız. Burada veritabanı işlemleri de var tabi ve repository ve entity sınıflarımızı hazlamıştık. ModelAndView nesnesi ile index view 'unu döndürüyor ve içerisine entry listesini ekliyoruz. Örnek:

		
@Controller
public class Maincontroller implements ErrorController
{
	private EntryRepository entryRepository;
	private List<Entry> all_entries;

	public Maincontroller(EntryRepository entryRepository)
	{
		this.entryRepository = entryRepository;
		this.all_entries = this.entryRepository.findAll(org.springframework.data.domain.Sort.by(org.springframework.data.domain.Sort.Order.desc("id")));
	}

	@GetMapping(path = { "", "/", "index" })
	public ModelAndView home()
	{
		ModelAndView modelAndView = new ModelAndView("index");
		modelAndView.addObject("entries", all_entries);
		return modelAndView;
	}

	@GetMapping(path = "entry")
	public ModelAndView entry(@RequestParam(name = "ent", required = true) Integer entry_id)
	{
		Entry entry = entryRepository.findById(entry_id).get();
		ModelAndView modelAndView = new ModelAndView("entry");
		modelAndView.addObject("entry", entry);
		modelAndView.addObject("content", "entries/" + entry_id + " :: " + entry_id + "");
		return modelAndView;
	}

	//	@GetMapping("/error")
	//	public String handleError(HttpServletRequest request)
	//	{
	//		Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
	//		if (status != null)
	//		{
	//			Integer statusCode = Integer.valueOf(status.toString());
	//			if (statusCode == HttpStatus.NOT_FOUND.value())
	//			{
	//				return "error-404";
	//			}
	//			if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value())
	//			{
	//				return "error-500";
	//			}
	//		}
	//		return "error-500";
	//	}
}
		
	

@Controller sayesinde spring boot ayağa kalkarken bu sınıfı ayağa kaldırır ve web isteklerini bu sınıfa yönlendirir. Constructor injection ile EntryRepository sınıfımız da spring boot ile ayağa kaldırılıp bu sınıfa dahil edilir. @GetMapping ise bu controller 'ın home metodunun / veya /index şeklinde gelen GET isteklerine cevap vereceğini belirtir. Bu şekilde template 'in index sayfasını getirmek için gereken back-end kodu hazır olmuş olur.

Bu post 'ları veritabanından çektikten sonra thymeleaf th:each yardımı ile ekranda listelemek için ise aşağıdakine benzer bir index sayfası kullanabilirsiniz:

			
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{site.title}"></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" th:content="#{site.description}">
<meta property="og:title" th:content="#{site.title}" />
<meta property="og:image" content="images/fav.png" />
<meta property="og:description" th:content="#{site.description}" />
<link rel="stylesheet" href="css/bootstrap.min.css">
</head>
<body>
	<div th:each="entry: ${entries}">
		<a th:href="@{'entry?ent=' + ${entry.id}}">
		<img th:src="@{'images/' + ${entry.id} + '/' + ${entry.imgurl}}" width="64" height="64">
		</a>
		<div>
			<h3>
				<a th:href="@{'entry?ent=' + ${entry.id}}" th:text="${entry.title}"></a>
			</h3>
			<div>
				<p>
					<span th:each="tag: ${entry.tags}">
						<a th:href="@{'tag?tag=' + ${tag.id}}">
							<text th:text="${tag.name}"></text>
						</a>
					</span>
				</p>
				<p>
					<span th:text="${#dates.format(entry.publishDate, 'dd MMMM yyyy')}">
					</span>
				</p>
			</div>
			<p th:text="${entry.summary}" />
			<p>
				<a th:href="@{'entry?ent=' + ${entry.id}}" th:text="#{site.read_more}"></a>
			</p>
		</div>
	</div>
	<script src="js/bootstrap.min.js"></script>
</body>
</html>
			
		

Tabi ki bu sayfalar da değişikliğe uğrayacaktır epeyce. Ben kolaylık ve örnek olması açısından bir listeleme yaptım. localhost:8080/index sayfasında aşağıdaki felaket ekran gibi listeleyebildim.

Bir de entry 'nin görüntülenmesi için /entry sayfası bulunması gerekecektir. Bu sayfada yazı içeriğini templates klasörü altındaki entries klasöründen thymeleaf replace metodu ile aldım. Bu sayede dinamik olarak bir sayfada istediğim yazıyı getirebildim. Bu şekilde yapmak zorunda değilsiniz, yazılarınızı veritabanından getirebilirsiniz. Benim böyle işime gelmişti bütün yazıları jar dosyası içinde tutmak için. Dosyaların yerleşimine baktığınızda images klasöründe de değişik title dosyaları ve resimler kullanılabileceğini fark edersiniz. Entry sayfası da örnek olarak şu şekilde olabilir:

			
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="${entry.title}"></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" th:content="${entry.summary}">
<meta name="author" content="Numan Karaaslan">
<meta property="og:type" content="article" />
<meta property="og:title" th:content="${entry.title}" />
<meta property="og:image" itemprop="image" th:content="@{'images/' + ${entry.imgurl}}" />
<meta property="og:description" th:content="${entry.summary}" />
<meta property="og:url" th:content="@{'http://www.numankaraaslan.com/entry?ent=' + ${entry.id}}" />
<link rel="stylesheet" href="css/bootstrap.min.css">
</head>
<body>
	<div th:replace="~{entries/1 :: 1}"></div>
<!-- 	<div th:replace="${content}"></div> -->
	<div>
		<div>
			<span th:text="${#dates.format(entry.publishDate, 'dd MMMM yyyy')}"></span>
		</div>
		<div>
			<a th:href="@{'tag?tag=' + ${tag.id}}" th:each="tag: ${entry.tags}" th:text="${tag.name}"></a>
		</div>
	</div>
	<script src="js/bootstrap.min.js"></script>
</body>
</html>
			
		

Buraya kadar bir sorun yok ise yine aşağıdaki berbat sayfadaki gibi dinamik olarak içeriği görüntüleyebilirsiniz. Etiketleri de dikkat ederseniz orada.

#5. Balık tutabiliyorsunuz artık

Blog yazılarınızı listeleyeceğiniz ve görüntüleyebileceğiniz ilk ekranlarınız bu kodlar ile hazır hale gelebiliyor. Bütün yazıyı okuduysanız size sürpriz olarak yazdığım kodların linkini de buraya bırakayım, indirip kullanabilirsiniz. Bu noktadan sonra kendi isterlerinize göre kendi konfigürasyonunuzu yapmalısınız. Yukarıdaki kod örneklerini 1 gün içerisinde hayata geçirmeniz mümkün. Eğer yapacağınız yazılımı analiz etmişseniz bundan sonraki kısım tamamen bu yazılımı buna benzer kodlar ile geliştirmeye, spring, hibernate ve thymeleaf 'in özelliklerini kullanmaya kalıyor. Controller sınıfınız zamanla büyüyecektir. Veritabanında tuttuğunuz bilgiler ve repository sınıfınız değişecektir. Bu yazının amacı temel çerçeveyi oturtmaktı. Sizlere hatasız kodlamalar diliyorum ve bir sonraki yazıda görüşmek üzere :)


Bir yorum yazabilirsiniz