MyBatis-Plusにおける条件ビルダー、カスタムSQL、Serviceインターフェースの設計について解説します。これらの機能を適切に活用することで、データアクセス層の実装効率と保守性を大きく向上させることができます。
条件ビルダーの活用
MyBatis-Plusは複雑なWHERE句を型安全に構築するためのAPIを提供しています。以下に代表的な使用例を示します。
特定条件でのカラム指定検索
ユーザ名に「o」を含み、残高が1000以上のレコードから特定カラムを取得する場合:
@Test
void fetchUsersWithFilter() {
LambdaQueryWrapper<Account> criteria = new LambdaQueryWrapper<>()
.select(Account::getId, Account::getUsername,
Account::getProfile, Account::getBalance)
.like(Account::getUsername, "o")
.ge(Account::getBalance, 1000);
List<Account> results = accountDao.selectList(criteria);
results.forEach(System.out::println);
}
条件付き更新処理
特定ユーザの残高を更新する場合、UpdateWrapperを使用します:
@Test
void modifyBalanceByName() {
UpdateWrapper<Account> updater = new UpdateWrapper<>()
.set("balance", 2000)
.eq("username", "jack");
accountDao.update(null, updater);
}
SQLのSET句に式を含める場合:
@Test
void deductBalanceBatch() {
List<Long> targetIds = Arrays.asList(1L, 3L, 5L);
UpdateWrapper<Account> updater = new UpdateWrapper<>()
.setSql("balance = balance - #{deduction}")
.in("id", targetIds);
accountDao.update(null, updater);
}
推奨事項:
- Lambda版のWrapper(
LambdaQueryWrapper・LambdaUpdateWrapper)を優先して使用し、文字列リテラルによるカラム名指定を回避- Query系は検索・削除・更新のWHERE句生成に、Update系は複雑なSET句が必要な場合に限定
カスタムSQLとの連携
WrapperでWHERE条件を構築し、残りのSQLは自作するハイブリッドアプローチを紹介します。
ユースケース:一括残高更新
指定ID群の残高を減算する処理を実装します。
Mapperインターフェース:
public interface AccountDao extends BaseMapper<Account> {
void batchDeductBalance(
@Param(Constants.WRAPPER) LambdaQueryWrapper<Account> criteria,
@Param("deduction") int deduction
);
}
XMLマッピング:
<update id="batchDeductBalance">
UPDATE account
SET balance = balance - #{deduction}
${ew.customSqlSegment}
</update>
実行テスト:
@Test
void executeCustomUpdate() {
List<Long> idList = Arrays.asList(1L, 3L, 5L);
int deductionAmount = 200;
LambdaQueryWrapper<Account> criteria = new LambdaQueryWrapper<>()
.in(Account::getId, idList);
accountDao.batchDeductBalance(criteria, deductionAmount);
}
ポイント:MapperパラメータのWrapperには
@Param(Constants.WRAPPER)または@Param("ew")を必ず付与します。
Service層の標準実装
MyBatis-Plusはビジネスロジック層の標準化もサポートしています。
インターフェース定義
public interface AccountService extends IService<Account> {
// 業務固有メソッドをここに追加
}
実装クラス
@Service
public class AccountServiceImpl
extends ServiceImpl<AccountDao, Account>
implements AccountService {
// 基本的なCRUDは継承により自動実装
}
利用例
@SpringBootTest
class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
void createNewAccount() {
Account entity = new Account();
entity.setUsername("Yamada");
entity.setPasswordHash("hashed_value");
entity.setContact("08012345678");
entity.setBalance(3000);
entity.setProfile("{\"age\": 28, \"dept\": \"sales\"}");
entity.setCreatedAt(LocalDateTime.now());
accountService.save(entity);
}
}
実装のベストプラクティス
| 機能 | 適用シナリオ | 注意点 |
|---|---|---|
| LambdaQueryWrapper | 動的条件の検索 | カラム変更時にコンパイルエラーで検出可能 |
| UpdateWrapper | 一括更新・条件付き更新 | setSql使用時はSQLインジェクション対策を確認 |
| カスタムSQL | 複雑なJOIN・サブクエリ | ${ew.customSqlSegment}の位置を正しく配置 |
| IService継承 | 標準的な業務処理 | 特殊なトランザクション制御が必要な場合はオーバーライド |
これらのパターンを組み合わせることで、単純なCRUDから複雑な業務ロジックまで、一貫した設計で対応できるデータアクセス層を構築できます。