MyBatis у Spring Boot: налаштування та робота

У Java-спільноті Hibernate framework де-факто вважається стандартом для зручної роботи з базою даних. Розробнику важко вибрати інший фреймворк, тому що іноді він не знає про існування альтернатив.

MyBatis не реалізує JPA спеки, а є альтернативою JPA. Основна відмінність MyBatis від Hibernate — це те, як здійснюється мапування об’єктів. Hibernate мапує таблиці БД на сутності, надаючи нам доступ до даних. Для отримання даних Hibernate генерує SQL-запити, а генеровані запити хороші до пори — до часу, а потім вони з’їдають купу часу, стають громіздкими і некерованими. MyBatis мапується не на таблиці, а на SQL-запити, за формування запитів відповідає розробник і тільки від нього буде залежати, як швидко буде працювати додаток.

MyBatis – це один з найпопулярніших Java-фреймворків для роботи з базами даних, який забезпечує зв’язок між об’єктами Java та SQL-запитами. На відміну від повноцінних ORM-фреймворків як Hibernate, MyBatis дає розробнику повний контроль над SQL-запитами, зберігаючи при цьому зручність маппінгу результатів на Java-об’єкти.

Переваги MyBatis

  • Повний контроль над SQL: можливість писати складні та оптимізовані запити
  • Простота налаштування: мінімальна конфігурація для початку роботи
  • Гнучкість: підтримка як анотацій, так і XML-конфігурацій
  • Продуктивність: відсутність overhead від складного ORM-маппінгу
  • Кешування: вбудована підтримка кешування на різних рівнях

Налаштування проекту

1. Додавання залежностей

Для роботи з MyBatis у Spring Boot додайте наступні залежності до pom.xml:

<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- MyBatis Spring Boot Starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>3.0.3</version>
    </dependency>
    
    <!-- MySQL Driver -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- PostgreSQL Driver (альтернативно) -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>
XML

2. Налаштування application.properties

# Налаштування бази даних
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# MyBatis налаштування
mybatis.mapper-locations=classpath:mappers/*.xml
mybatis.type-aliases-package=com.example.model
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.default-fetch-size=100
mybatis.configuration.default-statement-timeout=30

# Логування SQL-запитів
logging.level.com.example.mapper=DEBUG
XSL

3. Створення моделі даних

package com.example.model;

public class User {
    private Long id;
    private String username;
    private String email;
    private String firstName;
    private String lastName;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    
    // Конструктори
    public User() {}
    
    public User(String username, String email, String firstName, String lastName) {
        this.username = username;
        this.email = email;
        this.firstName = firstName;
        this.lastName = lastName;
    }
    
    // Геттери та сеттери
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    // ... інші геттери та сеттери
}
Java

Створення Mapper-ів

1. Mapper інтерфейс з анотаціями

package com.example.mapper;

import com.example.model.User;
import org.apache.ibatis.annotations.*;
import java.util.List;

@Mapper
public interface UserMapper {
    
    @Select("SELECT * FROM users WHERE id = #{id}")
    User findById(@Param("id") Long id);
    
    @Select("SELECT * FROM users")
    List<User> findAll();
    
    @Insert("INSERT INTO users(username, email, first_name, last_name, created_at) " +
            "VALUES(#{username}, #{email}, #{firstName}, #{lastName}, NOW())")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
    
    @Update("UPDATE users SET username=#{username}, email=#{email}, " +
            "first_name=#{firstName}, last_name=#{lastName}, updated_at=NOW() " +
            "WHERE id=#{id}")
    int update(User user);
    
    @Delete("DELETE FROM users WHERE id = #{id}")
    int deleteById(@Param("id") Long id);
    
    @Select("SELECT * FROM users WHERE username LIKE CONCAT('%', #{username}, '%')")
    List<User> findByUsernameContaining(@Param("username") String username);
}
Java

2. XML Mapper файли

Створіть файл src/main/resources/mappers/UserMapper.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.example.mapper.UserMapper">
    
    <!-- Result Map для складного маппінгу -->
    <resultMap id="UserResultMap" type="User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="email" column="email"/>
        <result property="firstName" column="first_name"/>
        <result property="lastName" column="last_name"/>
        <result property="createdAt" column="created_at"/>
        <result property="updatedAt" column="updated_at"/>
    </resultMap>
    
    <!-- Складний запит з JOIN -->
    <select id="findUsersWithPosts" resultMap="UserWithPostsResultMap">
        SELECT u.*, p.id as post_id, p.title, p.content
        FROM users u
        LEFT JOIN posts p ON u.id = p.user_id
        WHERE u.id = #{userId}
    </select>
    
    <!-- Динамічний запит -->
    <select id="findUsersByFilter" resultMap="UserResultMap">
        SELECT * FROM users
        <where>
            <if test="username != null and username != ''">
                AND username LIKE CONCAT('%', #{username}, '%')
            </if>
            <if test="email != null and email != ''">
                AND email LIKE CONCAT('%', #{email}, '%')
            </if>
            <if test="firstName != null and firstName != ''">
                AND first_name LIKE CONCAT('%', #{firstName}, '%')
            </if>
        </where>
        ORDER BY created_at DESC
    </select>
    
    <!-- Batch операції -->
    <insert id="insertUsers" parameterType="java.util.List">
        INSERT INTO users(username, email, first_name, last_name, created_at)
        VALUES
        <foreach collection="list" item="user" separator=",">
            (#{user.username}, #{user.email}, #{user.firstName}, #{user.lastName}, NOW())
        </foreach>
    </insert>
    
</mapper>
XML

Створення Service шару

package com.example.service;

import com.example.mapper.UserMapper;
import com.example.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
@Transactional
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    public User findById(Long id) {
        return userMapper.findById(id);
    }
    
    public List<User> findAll() {
        return userMapper.findAll();
    }
    
    public User save(User user) {
        if (user.getId() == null) {
            userMapper.insert(user);
        } else {
            userMapper.update(user);
        }
        return user;
    }
    
    public void deleteById(Long id) {
        userMapper.deleteById(id);
    }
    
    public List<User> searchByUsername(String username) {
        return userMapper.findByUsernameContaining(username);
    }
    
    @Transactional
    public void createUsersInBatch(List<User> users) {
        // Обробка великих списків порціями
        int batchSize = 1000;
        for (int i = 0; i < users.size(); i += batchSize) {
            int end = Math.min(i + batchSize, users.size());
            List<User> batch = users.subList(i, end);
            userMapper.insertUsers(batch);
        }
    }
}
Java

Створення Controller

package com.example.controller;

import com.example.model.User;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.findById(id);
        if (user != null) {
            return ResponseEntity.ok(user);
        }
        return ResponseEntity.notFound().build();
    }
    
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User savedUser = userService.save(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
        user.setId(id);
        User updatedUser = userService.save(user);
        return ResponseEntity.ok(updatedUser);
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteById(id);
        return ResponseEntity.noContent().build();
    }
    
    @GetMapping("/search")
    public List<User> searchUsers(@RequestParam String username) {
        return userService.searchByUsername(username);
    }
}
Java

Конфігурація MyBatis

1. Основна конфігурація

package com.example.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;

@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {
    
    @Autowired
    private DataSource dataSource;
    
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setMapperLocations(
            new PathMatchingResourcePatternResolver()
                .getResources("classpath:mappers/*.xml")
        );
        return sessionFactory.getObject();
    }
}
Java

2. Налаштування кешування

<!-- У MyBatis XML mapper -->
<cache 
    eviction="LRU"
    flushInterval="60000"
    size="512"
    readOnly="true"/>
XML

Розширені можливості

1. Використання TypeHandler

@MappedTypes(Status.class)
public class StatusTypeHandler extends BaseTypeHandler<Status> {
    
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Status parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter.name());
    }
    
    @Override
    public Status getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String status = rs.getString(columnName);
        return status != null ? Status.valueOf(status) : null;
    }
    
    // ... інші методи
}
Java

2. Плагіни MyBatis

@Intercepts({
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class AuditPlugin implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // Логіка аудиту
        System.out.println("SQL executed: " + invocation.getMethod().getName());
        return invocation.proceed();
    }
}
Java

Тестування

1. Unit тести

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    
    @Mock
    private UserMapper userMapper;
    
    @InjectMocks
    private UserService userService;
    
    @Test
    void testFindById() {
        // Given
        Long userId = 1L;
        User expectedUser = new User("testuser", "test@example.com", "Test", "User");
        expectedUser.setId(userId);
        when(userMapper.findById(userId)).thenReturn(expectedUser);
        
        // When
        User actualUser = userService.findById(userId);
        
        // Then
        assertThat(actualUser).isEqualTo(expectedUser);
        verify(userMapper).findById(userId);
    }
}
Java

2. Інтеграційні тести

@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
class UserMapperIntegrationTest {
    
    @Autowired
    private UserMapper userMapper;
    
    @Test
    @Transactional
    @Rollback
    void testCrudOperations() {
        // Create
        User user = new User("testuser", "test@example.com", "Test", "User");
        int insertResult = userMapper.insert(user);
        assertThat(insertResult).isEqualTo(1);
        assertThat(user.getId()).isNotNull();
        
        // Read
        User foundUser = userMapper.findById(user.getId());
        assertThat(foundUser).isNotNull();
        assertThat(foundUser.getUsername()).isEqualTo("testuser");
        
        // Update
        foundUser.setUsername("updateduser");
        int updateResult = userMapper.update(foundUser);
        assertThat(updateResult).isEqualTo(1);
        
        // Delete
        int deleteResult = userMapper.deleteById(user.getId());
        assertThat(deleteResult).isEqualTo(1);
    }
}
Java

Найкращі практики

1. Організація коду

  • Розділяйте прості запити (анотації) та складні (XML)
  • Використовуйте ResultMap для складного маппінгу
  • Групуйте пов’язані mapper-и в окремих пакетах

2. Продуктивність

  • Використовуйте пагінацію для великих наборів даних
  • Оптимізуйте SQL-запити та індекси
  • Налаштуйте кешування на рівні MyBatis

3. Безпека

  • Завжди використовуйте параметризовані запити
  • Валідуйте вхідні дані
  • Налаштуйте правильні права доступу до БД

Висновок

MyBatis у Spring Boot надає потужний та гнучкий інструмент для роботи з базами даних. Він поєднує простоту налаштування Spring Boot з повним контролем над SQL-запитами, що робить його ідеальним вибором для проектів, де важлива продуктивність та контроль над базою даних.

  • javaadmin

    Супер крутий Dev

    Related Posts

    Dependency Injection у Spring: пояснюємо на пальцях

    Коли програміст-початківець вперше відкриває документацію до Spring Framework, на нього одразу висипається купа грізних термінів: Dependency Injection (DI), Inversion of Control (IoC), ApplicationContext, Bean. Здається, що це вища математика. Ми вже розглядали DI в минулих матеріалах але цю дуже не просту тему краще розібрати по болтиках для повного розуміння. Насправді за цими розумними словами ховається надзвичайно проста та красива ідея. Це історія про те, як перестати збирати складні речі вручну й довірити…

    Dependency Injection в Java

    Dependency Injection (впровадження залежностей) — один з фундаментальних патернів проектування в сучасній Java-розробці. Цей принцип допомагає створювати гнучкі, тестовані та підтримувані додатки. Давайте розберемося, що це таке і як правильно його застосовувати. Що таке Dependency Injection? Dependency Injection — це техніка, при якій об’єкт отримує свої залежності ззовні, а не створює їх самостійно. Замість того, щоб клас сам інстанціював потрібні йому об’єкти, вони передаються йому через конструктор, setter-методи…

    Залишити відповідь

    Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

    Цікаве

    Dependency Injection у Spring: пояснюємо на пальцях

    • Автор javaadmin
    • 31 Липня, 2026
    • 15 views
    Dependency Injection у Spring: пояснюємо на пальцях

    Java Collections Framework: List, Set чи Map?

    • Автор javaadmin
    • 31 Липня, 2026
    • 29 views
    Java Collections Framework: List, Set чи Map?

    Оптимізуємо це: GraalVM Native Image, Leyden та CRaC

    • Автор javaadmin
    • 17 Липня, 2026
    • 57 views
    Оптимізуємо це: GraalVM Native Image, Leyden та CRaC

    JBang: Як вивчати Java легко

    • Автор javaadmin
    • 12 Травня, 2026
    • 131 views
    JBang: Як вивчати Java легко

    Agent Skills: Що це таке і чому про це варто знати

    • Автор javaadmin
    • 13 Квітня, 2026
    • 233 views
    Agent Skills: Що це таке і чому про це варто знати

    Dependency Injection в Java

    • Автор javaadmin
    • 9 Грудня, 2025
    • 407 views
    Dependency Injection в Java