У 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=DEBUGXSL
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);
}Java2. 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-запитами, що робить його ідеальним вибором для проектів, де важлива продуктивність та контроль над базою даних.





