MyBatis Plusとは?初心者でもわかる基礎解説
MyBatis Plusが解決する3つの開発課題
MyBatis Plusは、MyBatisを拡張した強力なパーシステンスフレームワークです。以下の3つの主要な開発課題を効果的に解決します:
- ボイラープレートコードの削減
- 従来のMyBatisでは必要だった基本的なCRUD操作のXMLマッピングが不要
- 標準的なSQL操作を自動生成
- アノテーションベースの簡潔な設定
- 開発生産性の向上
- AutoMapperによる自動マッピング機能
- コード生成ツールの提供
- 豊富なプラグインエコシステム
- 保守性とスケーラビリティの改善
- 統一された操作インターフェース
- クリーンなアーキテクチャの促進
- パフォーマンス最適化機能の組み込み
従来のMyBatisと比べて何が違う?
以下の表で、主要な違いを比較します:
| 機能 | MyBatis | MyBatis Plus |
|---|---|---|
| CRUD操作 | XML定義が必要 | アノテーションのみで可能 |
| ページネーション | 手動実装が必要 | 組み込み機能として提供 |
| 楽観的ロック | 独自実装が必要 | @Versionアノテーションで対応 |
| 論理削除 | 独自実装が必要 | @TableLogicで自動対応 |
| コード生成 | 外部ツールが必要 | 統合ツールとして提供 |
| 動的テーブル名 | 複雑な設定が必要 | 簡単な設定で対応可能 |
導入による具体的なメリット
- 開発時間の短縮
- 基本的なCRUD操作が数行で実装可能
- コード生成機能による初期コードの自動生成
- 豊富なユーティリティメソッドの提供
- 保守性の向上
- 統一された操作インターフェース
- 標準化されたコーディングスタイル
- 充実したドキュメントとコミュニティサポート
- 拡張性の確保
- プラグインによる機能拡張
- カスタムインジェクターの実装
- 独自の最適化戦略の適用
- パフォーマンスの最適化
- SQLインジェクション防止
- N+1問題の解決支援
- 効率的なバッチ処理の実装
技術的特徴
// 従来のMyBatisでのRepository実装
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User selectById(Long id);
@Insert("INSERT INTO users(name, age) VALUES(#{name}, #{age})")
int insert(User user);
// 他のCRUD操作も同様に実装が必要
}
// MyBatis Plusでの実装
public interface UserMapper extends BaseMapper<User> {
// 基本的なCRUD操作は自動で提供される
// カスタム操作のみを追加定義
}
このように、MyBatis Plusは従来のMyBatisの機能を完全に継承しながら、より効率的で保守性の高い開発を可能にします。特に、エンタープライズアプリケーションの開発において、その真価を発揮します。
【環境構築】5分で始めるMyBatis Plus導入手順
必要な依存関係の追加方法
Maven での設定
<!-- pom.xmlに以下の依存関係を追加 -->
<dependencies>
<!-- MyBatis Plus本体 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!-- データベースドライバ(MySQL使用時の例) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- コード生成ツール(オプション) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.5</version>
</dependency>
</dependencies>
Gradle での設定
// build.gradleに以下の依存関係を追加
dependencies {
implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.5'
implementation 'mysql:mysql-connector-java:8.0.33'
implementation 'com.baomidou:mybatis-plus-generator:3.5.5'
}
Spring Bootでの基本設定
1. アプリケーション設定
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_database
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
# SQL実行ログの出力(開発時のみ推奨)
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# マップアンダースコア型のカラム名をキャメルケースのプロパティにマッピング
map-underscore-to-camel-case: true
global-config:
db-config:
# プライマリーキーの自動採番方式(AUTO、INPUT、ASSIGN_ID、ASSIGN_UUID)
id-type: ASSIGN_ID
# テーブルの接頭辞設定(オプション)
table-prefix: t_
# 論理削除フィールドの設定
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
2. エンティティクラスの作成
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
@Data
@TableName("users") // テーブル名の指定
public class User {
@TableId(type = IdType.ASSIGN_ID) // プライマリーキーの設定
private Long id;
@TableField("user_name") // カラム名の指定
private String userName;
private Integer age;
@TableLogic // 論理削除フィールド
private Integer deleted;
@Version // 楽観的ロック用のバージョンフィールド
private Integer version;
}
3. Mapperインターフェースの作成
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
// BaseMapperを継承するだけで基本的なCRUD操作が使用可能
}
4. サービス層の実装
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
// ServiceImplを継承することで、より高度な操作メソッドが使用可能
}
設定確認のためのテストコード
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void testCrud() {
// ユーザーの作成
User user = new User();
user.setUserName("テストユーザー");
user.setAge(25);
userService.save(user);
// ユーザーの取得
User saved = userService.getById(user.getId());
assert saved != null;
assert saved.getUserName().equals("テストユーザー");
}
}
主な設定オプション一覧
| 設定項目 | 説明 | デフォルト値 |
|---|---|---|
| map-underscore-to-camel-case | スネークケースからキャメルケースへの自動変換 | true |
| id-type | プライマリーキーの生成方式 | ASSIGN_ID |
| table-prefix | テーブル名の接頭辞 | なし |
| logic-delete-field | 論理削除フィールド名 | なし |
| capital-mode | 大文字テーブル名の使用 | false |
| refresh-mapper | XMLの熱リロード | true |
CRUD操作を劇的に簡略化!基本機能の使い方
アノテーションだけで実装するテーブル連携
基本的なエンティティ設定
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("t_product")
public class Product {
// IDの自動生成(雪花アルゴリズム)
@TableId(type = IdType.ASSIGN_ID)
private Long id;
// カラム名が異なる場合の明示的なマッピング
@TableField("product_name")
private String name;
private BigDecimal price;
// 特定のフィールドをカラムとして扱わない
@TableField(exist = false)
private String tempField;
// 楽観的ロック用のバージョン管理
@Version
private Integer version;
// 論理削除フラグ
@TableLogic
private Integer deleted;
// 自動填充機能
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
自動填充の実装例
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 新規登録時の自動設定
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新時の自動設定
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
たった3行で実装できるCRUD操作
基本的なCRUD操作例
@Service
@RequiredArgsConstructor
public class ProductService extends ServiceImpl<ProductMapper, Product> {
// 新規登録
public void createProduct() {
Product product = new Product();
product.setName("新商品");
product.setPrice(new BigDecimal("1000"));
// 1行で登録完了
save(product);
}
// 一件取得
public Product getProduct(Long id) {
// 1行で取得完了
return getById(id);
}
// 更新
public void updateProduct(Long id) {
Product product = getById(id);
product.setPrice(new BigDecimal("2000"));
// 1行で更新完了
updateById(product);
}
// 削除
public void deleteProduct(Long id) {
// 1行で削除完了(論理削除の場合は自動的に論理削除になる)
removeById(id);
}
// バッチ操作
public void batchOperation() {
// バッチ登録
List<Product> products = Arrays.asList(
new Product().setName("商品1").setPrice(new BigDecimal("1000")),
new Product().setName("商品2").setPrice(new BigDecimal("2000"))
);
saveBatch(products);
// バッチ更新
updateBatchById(products);
// バッチ削除
removeBatchByIds(Arrays.asList(1L, 2L, 3L));
}
}
カスタムSQLの書き方とベストプラクティス
1. アノテーションベースの方法
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
// SELECT文の例
@Select("SELECT * FROM t_product WHERE price > #{minPrice}")
List<Product> findExpensiveProducts(@Param("minPrice") BigDecimal minPrice);
// UPDATE文の例
@Update("UPDATE t_product SET stock = stock - #{quantity} WHERE id = #{productId}")
int decreaseStock(@Param("productId") Long productId, @Param("quantity") Integer quantity);
// 動的SQLの例
@Select("""
SELECT * FROM t_product
WHERE 1=1
<if test="categoryId != null">
AND category_id = #{categoryId}
</if>
<if test="minPrice != null">
AND price >= #{minPrice}
</if>
ORDER BY create_time DESC
""")
List<Product> findByCriteria(@Param("categoryId") Long categoryId, @Param("minPrice") BigDecimal minPrice);
}
2. Wrapper APIを使用した動的クエリ
@Service
@RequiredArgsConstructor
public class ProductService extends ServiceImpl<ProductMapper, Product> {
public List<Product> searchProducts(String name, BigDecimal minPrice, BigDecimal maxPrice) {
// QueryWrapperを使用した動的クエリ構築
QueryWrapper<Product> queryWrapper = new QueryWrapper<Product>()
.like(StringUtils.isNotBlank(name), "product_name", name)
.ge(minPrice != null, "price", minPrice)
.le(maxPrice != null, "price", maxPrice)
.orderByDesc("create_time");
return list(queryWrapper);
}
public List<Product> advancedSearch(ProductSearchDTO searchDTO) {
// LambdaQueryWrapperを使用した型安全なクエリ構築
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<Product>()
.like(StringUtils.isNotBlank(searchDTO.getName()), Product::getName, searchDTO.getName())
.between(
searchDTO.getMinPrice() != null && searchDTO.getMaxPrice() != null,
Product::getPrice,
searchDTO.getMinPrice(),
searchDTO.getMaxPrice()
)
.in(CollectionUtils.isNotEmpty(searchDTO.getCategoryIds()),
Product::getCategoryId,
searchDTO.getCategoryIds()
);
return list(wrapper);
}
}
ベストプラクティス
- 性能最適化のポイント
- バッチ操作の活用
- インデックスの適切な設定
- 必要なカラムのみの取得
// 必要なカラムのみを取得する例
List<Product> products = new LambdaQueryWrapper<Product>()
.select(Product::getId, Product::getName, Product::getPrice)
.list();
- トランザクション管理
@Transactional(rollbackFor = Exception.class)
public void complexOperation() {
// 商品の登録
Product product = new Product();
product.setName("新商品");
save(product);
// 在庫の登録
Stock stock = new Stock();
stock.setProductId(product.getId());
stockMapper.insert(stock);
// エラーが発生した場合は全て自動ロールバック
}
- 楽観的ロックの活用
@Transactional
public boolean updateStock(Long productId, Integer quantity) {
Product product = getById(productId);
product.setStock(product.getStock() - quantity);
// バージョンチェックが自動的に行われる
return updateById(product);
}
- カスタムSQLとWrapper APIの使い分け
- 単純な条件検索 → Wrapper API
- 複雑なJOIN、集計 → カスタムSQL
- パフォーマンスが重要な場合 → カスタムSQL
開発効率を2倍にする!実践的な活用テクニック
ページネーション機能の実装方法
基本的なページネーション
@Service
@RequiredArgsConstructor
public class ProductService extends ServiceImpl<ProductMapper, Product> {
public IPage<Product> getProductPage(Integer pageNum, Integer pageSize) {
// ページネーションオブジェクトの作成
Page<Product> page = new Page<>(pageNum, pageSize);
// ソート条件の追加
page.addOrder(OrderItem.desc("create_time"));
// ページング検索の実行
return page(page);
}
// 検索条件付きページネーション
public IPage<Product> searchProductPage(ProductSearchDTO searchDTO) {
Page<Product> page = new Page<>(searchDTO.getPageNum(), searchDTO.getPageSize());
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<Product>()
.like(StringUtils.isNotBlank(searchDTO.getName()),
Product::getName, searchDTO.getName())
.ge(searchDTO.getMinPrice() != null,
Product::getPrice, searchDTO.getMinPrice())
.orderByDesc(Product::getCreateTime);
return page(page, wrapper);
}
}
カスタムページネーション
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
// 複雑なJOINを含むページネーション
@Select("""
SELECT p.*, c.category_name, s.stock_quantity
FROM t_product p
LEFT JOIN t_category c ON p.category_id = c.id
LEFT JOIN t_stock s ON p.id = s.product_id
WHERE p.deleted = 0
${ew.customSqlSegment}
""")
IPage<ProductDTO> getProductWithDetails(Page<ProductDTO> page,
@Param("ew") QueryWrapper<Product> wrapper);
}
// サービス層での使用
@Service
public class ProductService extends ServiceImpl<ProductMapper, Product> {
public IPage<ProductDTO> getProductDetailsPage(ProductSearchDTO searchDTO) {
Page<ProductDTO> page = new Page<>(searchDTO.getPageNum(), searchDTO.getPageSize());
QueryWrapper<Product> wrapper = new QueryWrapper<Product>()
.like(StringUtils.isNotBlank(searchDTO.getName()), "p.product_name", searchDTO.getName())
.ge(searchDTO.getMinPrice() != null, "p.price", searchDTO.getMinPrice())
.orderByDesc("p.create_time");
return baseMapper.getProductWithDetails(page, wrapper);
}
}
動的SQLを使いこなすコツ
1. 条件構築パターン
public class QueryHelper {
public static <T> LambdaQueryWrapper<T> buildWrapper(Consumer<LambdaQueryWrapper<T>> consumer) {
LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>();
consumer.accept(wrapper);
return wrapper;
}
}
// 使用例
@Service
public class ProductService extends ServiceImpl<ProductMapper, Product> {
public List<Product> searchProducts(ProductSearchDTO searchDTO) {
return list(QueryHelper.buildWrapper(w -> {
// 検索条件を関数型で構築
Optional.ofNullable(searchDTO.getName())
.ifPresent(name -> w.like(Product::getName, name));
Optional.ofNullable(searchDTO.getCategoryId())
.ifPresent(categoryId -> w.eq(Product::getCategoryId, categoryId));
Optional.ofNullable(searchDTO.getStatus())
.ifPresent(status -> w.eq(Product::getStatus, status));
}));
}
}
2. 複雑な検索条件の実装
@Service
public class ProductService extends ServiceImpl<ProductMapper, Product> {
// ネストされた条件の例
public List<Product> advancedSearch(ProductSearchDTO searchDTO) {
return list(new LambdaQueryWrapper<Product>()
.nested(w ->
w.like(Product::getName, searchDTO.getName())
.or()
.like(Product::getDescription, searchDTO.getKeyword())
)
.and(w ->
w.ge(Product::getPrice, searchDTO.getMinPrice())
.le(Product::getPrice, searchDTO.getMaxPrice())
)
.orderByDesc(Product::getCreateTime));
}
// OR条件の組み合わせ
public List<Product> searchByMultipleCategories(List<Long> categoryIds,
List<String> tags) {
return list(new LambdaQueryWrapper<Product>()
.and(w ->
w.in(Product::getCategoryId, categoryIds)
.or()
.in(Product::getTags, tags)
)
.orderByDesc(Product::getCreateTime));
}
}
パフォーマンスを最適化するためのヒント
1. バッチ処理の最適化
@Service
@Transactional(rollbackFor = Exception.class)
public class BatchService {
private static final int BATCH_SIZE = 1000;
@Autowired
private ProductService productService;
public void batchInsert(List<Product> products) {
// リストを適切なサイズに分割して処理
Lists.partition(products, BATCH_SIZE).forEach(batch -> {
productService.saveBatch(batch, BATCH_SIZE);
});
}
// バッチ更新の最適化
public void batchUpdate(List<Product> products) {
Lists.partition(products, BATCH_SIZE).forEach(batch -> {
productService.updateBatchById(batch, BATCH_SIZE);
});
}
}
2. クエリパフォーマンスの最適化
@Service
public class ProductService extends ServiceImpl<ProductMapper, Product> {
// 必要なカラムのみを取得
public List<ProductSimpleDTO> getProductList() {
return list(new LambdaQueryWrapper<Product>()
.select(Product::getId, Product::getName, Product::getPrice)
.orderByDesc(Product::getCreateTime));
}
// EXISTS句の活用
public boolean hasAvailableProducts(Long categoryId) {
return exists(new LambdaQueryWrapper<Product>()
.eq(Product::getCategoryId, categoryId)
.eq(Product::getStatus, ProductStatus.AVAILABLE));
}
// COUNT最適化
public long countAvailableProducts() {
return count(new LambdaQueryWrapper<Product>()
.select(Product::getId) // IDのみをカウント
.eq(Product::getStatus, ProductStatus.AVAILABLE));
}
}
3. キャッシュ戦略
@Service
@CacheConfig(cacheNames = "product")
public class ProductService extends ServiceImpl<ProductMapper, Product> {
@Cacheable(key = "#id")
@Override
public Product getById(Serializable id) {
return super.getById(id);
}
@CacheEvict(key = "#id")
@Override
public boolean removeById(Serializable id) {
return super.removeById(id);
}
@CachePut(key = "#product.id")
@Override
public boolean updateById(Product product) {
return super.updateById(product);
}
// キャッシュを使用した高速な存在チェック
@Cacheable(key = "'exists:' + #id")
public boolean existsById(Long id) {
return exists(new LambdaQueryWrapper<Product>()
.eq(Product::getId, id));
}
}
パフォーマンス最適化のベストプラクティス
- インデックス戦略
- 検索条件によく使用されるカラムにインデックスを作成
- 複合インデックスの適切な設計
- インデックスの効果を確認するためのEXPLAIN分析
- メモリ使用量の最適化
- ページネーションの適切な使用
- 大量データ処理時のストリーム活用
- 適切なバッチサイズの設定
- クエリの最適化
- 必要なカラムのみを選択
- JOINの最小化
- サブクエリの適切な使用
現場で使える!トラブルシューティングとTips
よくあるエラーと解決方法
1. マッピング関連のエラー
問題1: Unknown Column Error
org.apache.ibatis.exceptions.PersistenceException: Error attempting to get column 'user_name' from result set. Cause: java.sql.SQLSyntaxErrorException: Unknown column 'user_name' in 'field list'
解決策:
// 正しいマッピング設定
@TableName("t_user")
public class User {
// カラム名が異なる場合は明示的にマッピング
@TableField("user_name")
private String userName;
// キャメルケース→スネークケースの自動変換を無効化する場合
@TableField(value = "user_name", strategy = FieldStrategy.NOT_NULL)
private String userName;
}
問題2: Type Handling Error
org.apache.ibatis.type.TypeException: Error setting non null for parameter #1 with JdbcType null
解決策:
// カスタムタイプハンドラーの実装
@MappedTypes(YourEnum.class)
public class YourEnumTypeHandler extends BaseTypeHandler<YourEnum> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, YourEnum parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter.getCode());
}
@Override
public YourEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
return YourEnum.fromCode(rs.getString(columnName));
}
// 他のメソッドも実装
}
// application.ymlでの設定
mybatis-plus:
type-handlers-package: com.your.package.handlers
2. パフォーマンス関連の問題
問題1: N+1問題
// 問題のあるコード
@Service
public class OrderService extends ServiceImpl<OrderMapper, Order> {
@Autowired
private UserService userService;
public List<OrderDTO> getOrders() {
// N+1問題を引き起こす実装
return list().stream()
.map(order -> {
OrderDTO dto = new OrderDTO();
// 各オーダーに対して個別にユーザー情報を取得
User user = userService.getById(order.getUserId()); // N回のクエリ発生
dto.setUserName(user.getName());
return dto;
})
.collect(Collectors.toList());
}
}
// 最適化された実装
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
@Select("""
SELECT o.*, u.name as user_name
FROM t_order o
LEFT JOIN t_user u ON o.user_id = u.id
""")
List<OrderDTO> getOrdersWithUser();
}
問題2: メモリ不足
// 問題のあるコード
public void processLargeData() {
// 全データを一度にメモリに読み込む
List<Product> products = list(); // メモリ不足の可能性
products.forEach(this::processProduct);
}
// 最適化された実装
public void processLargeData() {
// ページング処理による最適化
long count = count();
int pageSize = 1000;
int pages = (int) Math.ceil(count / (double) pageSize);
for (int i = 1; i <= pages; i++) {
Page<Product> page = new Page<>(i, pageSize);
page(page).getRecords().forEach(this::processProduct);
}
}
デバッグのコツと効率的なログ設定
1. SQLログの設定
# application.yml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
level:
com.baomidou.mybatisplus: debug
com.your.mapper.package: debug
2. カスタムインターセプターによるSQL監視
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class})
})
@Component
public class SqlMonitorInterceptor implements Interceptor {
private static final Logger log = LoggerFactory.getLogger(SqlMonitorInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long endTime = System.currentTimeMillis();
long sqlCost = endTime - startTime;
MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
String sqlId = statement.getId();
log.info("SQL実行時間 - ID: {}, コスト: {}ms", sqlId, sqlCost);
if (sqlCost > 1000) {
log.warn("スロークエリ検出 - ID: {}, コスト: {}ms", sqlId, sqlCost);
}
}
}
}
3. デバッグモードでの開発効率化
@Slf4j
@Service
public class DebugService {
// デバッグ用のユーティリティメソッド
public void debugQuery(LambdaQueryWrapper<Product> wrapper) {
String sql = SqlUtils.showSql(wrapper);
log.debug("生成されたSQL: {}", sql);
// パラメータの出力
Map<String, Object> params = wrapper.getParamNameValuePairs();
log.debug("パラメータ: {}", params);
}
}
本番環境での運用ベストプラクティス
1. パフォーマンスモニタリング
@Aspect
@Component
@Slf4j
public class PerformanceMonitorAspect {
@Around("execution(* com.your.package.mapper.*Mapper.*(..))")
public Object monitorMapperMethods(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
long startTime = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long executionTime = System.currentTimeMillis() - startTime;
if (executionTime > 500) {
log.warn("スロークエリ検出: {} - 実行時間: {}ms", methodName, executionTime);
// メトリクス収集システムへの記録
MetricsCollector.recordSlowQuery(methodName, executionTime);
}
}
}
}
2. 本番環境での設定最適化
# application-prod.yml
mybatis-plus:
configuration:
# キャッシュを有効化
cache-enabled: true
# ログ出力を必要最小限に
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
global-config:
db-config:
# バッチサイズの最適化
batch-size: 1000
# IDタイプの設定
id-type: assign_id
# 論理削除の設定
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
# バナー(起動時のロゴ表示)を無効化
banner: false
3. セキュリティ対策
// SQLインジェクション対策
@Service
public class SecureService {
// 安全なLike検索の実装
public List<Product> searchProducts(String keyword) {
// キーワードのエスケープ処理
String escapedKeyword = SqlUtils.escapeSQL(keyword);
return list(new LambdaQueryWrapper<Product>()
.like(Product::getName, escapedKeyword));
}
// 権限チェックの実装
@PreAuthorize("hasRole('ADMIN')")
public void deleteProduct(Long id) {
removeById(id);
}
}
運用上の重要なチェックポイント
- パフォーマンスモニタリング
- スロークエリの監視
- コネクションプールの最適化
- メモリ使用量の監視
- セキュリティ対策
- SQLインジェクション対策
- アクセス権限の適切な設定
- センシティブデータの暗号化
- バックアップと復旧
- 定期的なバックアップ
- リストア手順の確認
- 障害時の切り戻し計画
- メンテナンス計画
- インデックスの再構築
- 統計情報の更新
- ログローテーション
まとめ:MyBatis Plusで実現する効率的なDB開発
本記事のポイント
- MyBatis Plusの主要な利点
- ボイラープレートコードの大幅な削減
- 開発生産性の向上
- 保守性とスケーラビリティの改善
- 豊富な機能と拡張性
- 実装のベストプラクティス
- アノテーションベースの簡潔な実装
- Wrapper APIを活用した動的クエリ
- 適切なページネーション処理
- パフォーマンスを考慮した実装
- 運用時の重要ポイント
- 効果的なログ設定とモニタリング
- 適切なキャッシュ戦略
- セキュリティ対策の実施
- トラブルシューティング手法の確立
次のステップ
- スキルアップの方向性
// 以下の順序での学習を推奨 Step 1: 基本的なCRUD操作の習得 Step 2: 動的クエリビルダーの活用 Step 3: パフォーマンス最適化手法の実践 Step 4: 高度な機能(プラグイン開発など)の習得
- 実践的な活用シーン
- マイクロサービスでのデータアクセス層実装
- レガシーシステムのリファクタリング
- 大規模データ処理システムの開発
- API開発プロジェクトでの活用
- 参考リソース
導入効果の指標
| 項目 | 従来の実装 | MyBatis Plus使用時 |
|---|---|---|
| CRUD実装時間 | 2-3時間 | 30分以内 |
| コード行数 | 100-200行 | 20-30行 |
| メンテナンスコスト | 高 | 低 |
| 学習コスト | 中 | 低-中 |
| パフォーマンス | 標準 | 最適化済み |
最後に
MyBatis Plusは、Java開発者にとって強力な武器となります。本記事で解説した内容を実践することで、以下のような効果が期待できます:
- 開発時間の50%以上削減
- コードの保守性向上
- チーム全体の生産性向上
- 高品質なデータアクセス層の実現
特に重要なのは、単なる機能の使用だけでなく、適切な設計とベストプラクティスの適用です。これにより、長期的なメンテナンス性とパフォーマンスの両立が可能となります。
今後のバージョンアップでさらなる機能追加が期待される中、基本的な概念と設計思想を理解することで、新機能もスムーズに活用できるでしょう。
Quick Reference
// 基本実装テンプレート
@Service
public class YourService extends ServiceImpl<YourMapper, YourEntity> {
// 基本的なCRUD操作
public void basicOperations() {
// 保存
save(entity);
// 検索
getById(id);
// 更新
updateById(entity);
// 削除
removeById(id);
}
// 応用操作
public void advancedOperations() {
// 条件検索
list(new LambdaQueryWrapper<YourEntity>()
.eq(YourEntity::getField, value));
// ページネーション
page(new Page<>(1, 10));
}
}
このコードテンプレートを起点に、プロジェクトの要件に応じて機能を拡張していくことで、効率的な開発が可能となります。
MyBatis Plusの導入は、モダンなJava開発における重要な一歩となるでしょう。本記事の内容を参考に、ぜひ実践してみてください。