MyBatis

ozhaneyp
Akbank Teknoloji
Published in
8 min readFeb 2, 2023

Bu yazımda sizlere Akbank Teknoloji olarak bizim de sık sık kullandığımız, avantajları ve kolaylığı ile öne çıkan bir Java Persistence Frameworkünden, MyBatis’ten bahsetmek istiyorum. Öncelikle, anlatacağım bu teknolojiyi daha iyi anlayabilmemiz için önemli olan Persistence Framework ve ORM (Object Relational Mapping) kavramlarını hatırlayalım.

Persistence Framework nedir ?

Basitçe persistence framework’ler, uygulamalar ve veri tabanları arasında bilgiyi alma, depolama ve bu işlemleri güvenli hale getirme görevlerini üstlenen ara katman yazılımlarıdır. Bu tip framework’lere MyBatis, Hybernate gibi yazılımları örnek olarak gösterebiliriz. Birçok persistence framework, ORM (Object Relational Mapping) tekniğini kullanır. Bu açıdan bu framework’lere ORM dendiğini de çokça duyarız.

ORM(Object Relational Mapping) nedir ?

Birçok persistence framework aynı zamanda bir ORM aracıdır diyebiliriz. ORM araçları, veri tabanımızdaki bilgileri uygulamamızdaki nesnelerle ilişkilendirebilirler.

MyBatis Nedir ?

MyBatis açık kaynaklı, lightweight (hafif) bir Java Persistence Framework’üdür. ORM tekniğini kullanır. Bu sayede veri tabanları ve nesneler arasındaki eşlemeyi sağlar.

MyBatis Kullanmanın Avantajları

  • Günümüzde en basit persistence framework’leri arasında yer almaktadır.
  • SQL sorgularını ve eşlemelerini bir XML dosyasına gömerek hızlıca uygulama ve veri tabanı etkileşimi yapılabilir.
  • Basmakalıp JDBC kodlarından bizi kurtarır.
  • Kendine ait özel bir dili yoktur. SQL’e vurgu yapar. Sanki bir veri tabanı sunucusu yönetim sisteminde sorgu yazıyormuşuz gibi SQL’i çok esnek bir yapıda kullanmamıza olanak sağlar.
  • Store procedure’ları, fonksiyonları ve dinamik SQL sorgularını kullanmamıza izin verir. Bu sayede SQL’in tüm özelliklerini kullanabiliriz.
  • İşlerimizi SQL ile hallettiğimizden ön bir derleme işlemi gerektirmez. Bu sayede diğer framework’lere göre daha hızlı çalıştığını söyleyebiliriz.
  • Veri tabanı işlemlerini ve eşlemelerini çok güzel bir biçimde soyutlar. SQL sorgularımızı Java kodlarımız ile birlikte görmeyiz.

Uygulama Hakkında

Projemizde Ne Yapacağız ?

Uygulamamızda student adında bir veri tabanı ve bu veri tabanı altında students adında bir tablo oluşturacağız. Ardından MyBatis ile select, insert, update, delete işlemlerini gerçekleştireceğiz.

Kullanacağımız Teknolojiler: MyBatis, Maven, Spring Boot, MySQL

NOT: Spring Boot, MyBatis için bize otomatik bir konfigürasyon sağlıyor. Ayrıca anotasyonları da kullanabildiğimizden MyBatis kısmını gerçekten kolaylaştırıyor.

UYGULAMA

1) MySQL’de Veri Tabanı ve Tablo Oluşturma

İlk olarak MySQL’de student adında bir veri tabanı oluşturalım. Ardından student veri tabanı içinde Students tablosu oluşturalım.

CREATE TABLE Students (
ID int NOT NULL AUTO_INCREMENT,
FIRST_NAME varchar(255) NOT NULL,
AGE int NOT NULL,
PRIMARY KEY (ID)
);

2) Spring Boot Projesi Oluşturma

Projemizi buradan oluşturabiliriz. Add Dependencies kısmında MySQL Driver ve MyBatis seçeneklerini seçelim ve bir Maven projesi oluşturalım.

pom.xml dosyamız şekildeki gibi gözükmektedir.

  <dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

3) Spring ve MyBatis Konfigürasyonu

Projemizi import ettiğimize göre application.properties dosyasından Spring’i ve MyBatis’i ayarlayalım.

<!-- İşletim sistemimizi hazırlamak için kullanacağımız driverı belirtiyoruz.
Projede MySQL kullandığımızdan MySQL driverini belirttik. -->
spring.datasource.driver-class-name: com.mysql.cj.jdbc.Driver

<!-- Bağlanacağımız veri tabanının URL'sini veriyoruz -->
spring.datasource.url = jdbc:mysql://localhost:3306/student

<!-- Veri tabanının username ve passwordunu veriyoruz -->
spring.datasource.username = root
spring.datasource.password = ******

<!-- Bu konfigürasyonda MyBatis'e, eşlemeleri ve sorguları hangi klasörün
altında yapacağımızı söylüyoruz. MyBatis bu klasörün(ismini Mapper yapacağız)
altındaki XML uzantılı dosyalara bakarak eşlemeleri ve sorguları çalıştıracak. -->
mybatis.mapper-locations = classpath:mapper/*.xml

<!-- XML uzantılı dosyalarda model sınıflarını kullanırken paket
isimleriyle vermemiz gerekiyor normalde. Bu konfigürasyonu yaptıktan sonra
paket isimlerini vermemize gerek yok. Yani 'com.oz.mybatiss.models.Student'
demek yerine 'Student' diyerek işlem yapabiliyoruz. -->
mybatis.type-aliases-package = com.oz.mybatiss.models**

<!-- Yazıları Camel Case tipinde yazacağım için bu özelliği "true" olarak
ayarlıyorum. Örnek verecek olursam 'Students' tablomuzdaki "FIRST_NAME" isimli
kolonumuzu 'Student' sınıfımız altındaki "firstName" properties ile
eşleşebilir. Bu konfigürasyonu yapmazsak tablodaki isim ve sınıftaki ismin
aynı olması gerekirdi. -->
mybatis.configuration.map-underscore-to-camel-case=true

Paketlerin, Dosyaların ve Sınıfların Oluşturulması

Şekilde görülen proje yapısını oluşturdum.

Projemizin Yapısı
  • models adında paket oluşturup altına Student sınıfını ekleyelim.
                       // MODEL SINIFIMIZ STUDENT

package com.oz.mybatiss.models;

public class Student {

private int id;
private String firstName;
private int age;

public Student() {

}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

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

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}
  • repositories paketi oluşturup altına ILocalStudentRepository interface’sini ekleyelim.

Kritik yerlerden birisi burası diyebiliriz. Oluşturduğumuz interface’deki fonksiyonları kullanarak veri tabanına istek atacağız ve veri tabanından cevap alacağız. Kısaca uygulama katmanında bu interface’yi arayüz olarak kullanıyoruz.

Peki bu interface’nin fonksiyonlarını nerede dolduracağız?

ILocalStudentRepository interface’sinin fonksiyonlarını,
mapper dosyası altında oluşturacağımız student_mapper.xml dosyasında mapping (eşleme) yapacağız. Spring’e bunu söylemek için @Mapper anotasyonunu kullanıyoruz.

// MAPPER INTERFACE ILocalStudentRepository

package com.oz.mybatiss.repositories;

import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.oz.mybatiss.models.Student;

@Mapper
public interface ILocalStudentRepository {

public void insertStudent(Student student);
public Student selectStudentById(int id);
public List<Student> selectStudentByFirstName(Student student);
public void updateStudent(Student student);
public void deleteStudentById(int id);
}

Proje akış şemasını şu şekil düşünebiliriz.

  • src/main/resources altında mapper klasörü oluşturalım ve içine student_mapper.xml dosyasını ekleyelim.

Artık ILocalStudentRepository’i MyBatis’e student_mapper.xml içinde tanıtabiliriz.

Eşleme ve Sorgu İşlemleri

<mapper>

<mapper namespace = "com.oz.mybatiss.repositories.ILocalStudentRepository">

</mapper>

Öncelikle student_mapper.xml dosyasına hangi interface’yi eşleyeceğini söylüyoruz. Bunu mapper tagı içinde namespace değişkenini setleyerek yapıyoruz. Bütün sorgularımızı ve fonksiyon tanıtımını bu <mapper> tagı içinde yapıyoruz.

Artık ILocalStudentRepository interface’sindeki fonksiyonları doldurabiliriz.

<insert>

MyBatis’e <insert> tagıyla insert işlemi yapacağımızı söyledik. Ardından eşleyeceğimiz fonksiyonun ismini id kısmına veriyoruz. Fonksiyon parametresini belirtmek için parameterType=”Student” yaptık.

NOT : İsimleri doğru eşleştirdiğimiz takdirde burada Map olarak da bir parametre verebiliriz.

Burada aslında parameterType kısmını hiç kullanmayabilirsiniz. Ancak daha okunabilir olduğu için kullanmanızı öneriyorum.

Ek olarak tabloya veri kayıt ettirirken hatırlarsanız ID kolonunu primary key ve otomatik artan olarak işaretlemiştik. Oluşturulacak id’nin otomatik olarak set edilmesi için useGeneratedKeys parametresini true olarak işaretledik.

Fonksiyonlara gönderdiğimiz parametre değerlerini burada “#{ }” ile getirdik. Student sınıfımız firstName ve age bilgilerini içerdiğinden #{firstName} ve #{age} değerlerini çağırabildik.

Gerisi tamamıyla SQL bilgisi diyebilirim.

<select>

Select işlemi yapacağımızdan <select> komutunu kullandık. Yeni olarak gördüğümüz resultType değeri var. resultType’ı fonksiyonun dönüş değeri olarak vermemiz gerek. Bu fonksiyonun dönüş değeri List<Student> olsaydı bile resultType=”Student yapsak sıkıntısız bir biçimde çalışırdı. MyBatis bu konuyu kendisi hallediyor.

Hemen örneğimizi verelim…

  <!-- public List<Student> selectStudentByFirstName(Student student);-->

<select id="selectStudentByFirstName" parameterType="Student" resultType="Student" >
SELECT * FROM students WHERE FIRST_NAME = #{firstName}
</select>

Burada birden fazla kişi, gelen isim değerine sahip olabilir. O yüzden dönüş değerimizi List<Student> olarak ayarladık. resultType=Student yaptık.

<update>

<!--         public void updateStudent(Student student)        -->

<update id = "updateStudent" parameterType = "Student">
UPDATE students SET FIRST_NAME = #{firstName}, AGE = #{age} WHERE ID = #{id}
</update>

<update> tagı ile MyBatis’e update işlemi yapacağımızı söyledik.

<delete>

           <!--  public void deleteStudentById(int id)    -->

<delete id="deleteStudentById" parameterType="Integer">
DELETE FROM students WHERE ID = #{id}
</delete>

Delete işlemi yapacağımızı belirtip, parametre olarak alınan id’ye sahip olan kullanıcımızı silmek için sorgumuzu yazdık.

student_mapper.xml

<!-- student_mapper.xml -->

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace = "com.oz.mybatiss.repositories.ILocalStudentRepository">

<insert id="insertStudent" parameterType="Student" useGeneratedKeys="true">
INSERT INTO students(FIRST_NAME,AGE) VALUES(#{firstName},#{age})
</insert>

<select id="selectStudentById" parameterType="Integer" resultType="Student" >
SELECT * FROM students WHERE ID = #{id}
</select>

<select id="selectStudentByFirstName" parameterType="String" resultType="Student" >
SELECT * FROM students WHERE FIRST_NAME = #{firstName}
</select>

<update id = "updateStudent" parameterType = "Student">
UPDATE students SET FIRST_NAME = #{firstName}, AGE = #{age} WHERE ID = #{id}
</update>

<delete id="deleteStudentById" parameterType="Integer">
DELETE FROM students WHERE ID = #{id}
</delete>

</mapper>

CRUD işlemlerimizi yapmak için artık sadece interface içinde tanımlı olan fonksiyonları uygulama katmanımızda çağırmamız yeterli.

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

Student student = new Student();
student.setFirstName("MYBATIS");
student.setAge(30);

this.localStudentRepository.insertStudent(student);
List<Student> students = this.localStudentRepository.selectStudentByFirstName("MYBATIS");
}

NOT: Sorgularımızı XML dosyalarında yazmak yerine anotasyon ile ILocalStudentRepository’nin fonksiyonlarının üstüne işaretleyebilirdik. Fakat bu hem performans kaybına sebep olacaktı hem de SQL’i uygulama katmanından daha az soyutlayacaktı. Bu yüzden XML kullanmanızı öneriyorum.

Dinamik SQL Sorguları

<if> & <where>

Burada 3 senaryo düşünelim:

1. İsme göre getirsin

2. Yaşa göre getirsin

3. İsme ve yaşa göre getirsin

Normalde bu 3 işlem için <if> tagı olmasa 3 tane sorgu yazmamız gerekirdi. <if> tagını kullanarak tek sorguda istediğiniz filtrelemeyi yapıp sonuçları getirebilirsiniz. Yapmanız gereken Student nesnesini oluştururken hangi özelliklere göre getirmesini istiyorsanız o özellikleri set etmenizdir.

// İsmi Ahmet ve yaşı 40 olan kişileri getir.
Student student = new Student();
student.setFirstName("Ahmet");
student.setAge(40);
List<Student> students = this.localStudentRepository.selectStudents(student);

// İsmi Ahmet olanları getir
Student student2 = new Student();
student2.setFirstName("Ahmet");
List<Student> students2 = this.localStudentRepository.selectStudents(student2);
<select id = "selectStudents" parameterType = "Student" resultType = "Student">
SELECT * FROM students

<where>

<if test="id != null">
ID = #{id}
</if>
<if test=" firstName != null">
AND FIRST_NAME= #{firstName}
</if>
<if test="age!= null">
AND AGE = #{age}
</if>

</where>

</select>

Burada <where> tagını neden kullandığımızı merak edebilirsiniz. Normalde SELECT * FROM STUDENTS WHERE yazıp sonra <if> tag’larını da koyabilirdik. Fakat bu senaryoda herhangi bir if durumuna girmezse sorgumuz SELECT * FROM STUDENTS WHERE şeklinde kalacaktı. <where> tagını kullanarak bu duruma karşı önlemimizi aldık.

<choose> & <otherwise>

Choose tag’ını switch case yapısı gibi düşünebiliriz.

<select id = "getStudents" parameterType = "Student" resultType = "Student">
SELECT * FROM students
<where>
<choose>
<when test = "firstName != null">
AND FIRST_NAME LIKE #{firstName}
</when>

<when test = "age != null">
AND AGE LIKE #{age}
</when>

<otherwise>
AND ID= #{id}
</otherwise>

</choose>
</where>
</select>

Burada <choose> tag’ı altında sadece 1 tane <when> tag’ı çalıştırılabilir (hangisine önce girerse o çalışır) veya hiçbir koşul sağlanmazsa <otherwise> tag’ı altındaki sorgu çalışır.

İkinci <when> tag’ı altına girdiğimizi varsayalım. Yani sorgumuz SELECT * FROM STUDENTS WHERE AND AGE LIKE #{age} şeklinde olacak. Sorguda WHERE AND kısmı yanlış aslında. Fakat MyBatis AND kelimesine karşı duyarlıdır ve bu kelimeyi otomatik bir şekilde ayarlayabilir. Yani üstteki sorgumuz sıkıntısız bir biçimde çalışır.

<set>

<select id="search" resultType="Student" parameterType="map">
SELECT * FROM students
<set>
<if test="firstName!= null">FIRST_NAME=#{firstName},</if>
<if test="age!= null">AGE=#{age},</if>
<if test="authorname != null">authorname=#{authorname},</if>
</set>
</select>

Fark ettiyseniz parameterType’a, Student nesnesi göndermek yerine bir map gönderiyoruz. Bu kullanımı da görmenizi istedim.

Burada yaptığımız iş yukarıda <choose> tag’ını kullanırken yaptığımız işle aynı aslında. <set> tag’ı altında if’lerden yalnızca tek if çalışacak (koşulu ilk sağlayan) veya koşullar sağlanmadıysa hiçbir if çalışmayacak.

<foreach>

<foreach> tag’ını IN anahtar kelimesiyle yazılmış sorguları dinamik bir şekilde yazmak için kullanabiliriz.

ID’si 1,2 ve 3 olan Student’larımızı getirmeye çalıştığımızı düşünelim. Burada 1,2 ve 3 id’lerini bir collection ile sorgumuza gönderebiliriz.

<select id = "getStudentsByMultipleID" resultType = "Student">
SELECT *
FROM STUDENT S
WHERE ID in

<foreach item = "item" index = "index" collection = "list"
open = "(" separator = "," close = ")">
#{item}
</foreach>

</select>

--

--