or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cache-system.mdindex.mdmapping-annotations.mdplugin-system.mdsession-management.mdtransaction-management.mdtype-handling.md

plugin-system.mddocs/

0

# Plugin System

1

2

Interceptor-based plugin system for AOP-style cross-cutting concerns like logging, performance monitoring, and custom behavior injection. MyBatis plugins provide powerful hooks into the core execution pipeline.

3

4

## Capabilities

5

6

### Interceptor Interface

7

8

Core plugin interface that allows interception of method calls on key MyBatis objects.

9

10

```java { .api }

11

/**

12

* Plugin interceptor interface for AOP-style method interception

13

*/

14

interface Interceptor {

15

/** Intercept method call and potentially modify behavior */

16

Object intercept(Invocation invocation) throws Throwable;

17

18

/** Wrap target object with proxy (default implementation available) */

19

default Object plugin(Object target) {

20

return Plugin.wrap(target, this);

21

}

22

23

/** Set configuration properties for the interceptor */

24

default void setProperties(Properties properties) {

25

// Default empty implementation

26

}

27

}

28

```

29

30

### Plugin Configuration Annotations

31

32

#### @Intercepts

33

34

Specifies which methods should be intercepted by the plugin.

35

36

```java { .api }

37

/**

38

* Specifies intercepted methods for plugin

39

*/

40

@interface Intercepts {

41

/** Array of method signatures to intercept */

42

Signature[] value();

43

}

44

```

45

46

#### @Signature

47

48

Defines specific method signature to intercept.

49

50

```java { .api }

51

/**

52

* Method signature for interception

53

*/

54

@interface Signature {

55

/** Target class containing the method */

56

Class<?> type();

57

58

/** Method name to intercept */

59

String method();

60

61

/** Method parameter types */

62

Class<?>[] args();

63

}

64

```

65

66

### Invocation Object

67

68

Wrapper for intercepted method calls providing access to target, method, and arguments.

69

70

```java { .api }

71

/**

72

* Method invocation wrapper for intercepted calls

73

*/

74

class Invocation {

75

/** Create invocation wrapper */

76

public Invocation(Object target, Method method, Object[] args);

77

78

/** Get target object being invoked */

79

public Object getTarget();

80

81

/** Get method being invoked */

82

public Method getMethod();

83

84

/** Get method arguments */

85

public Object[] getArgs();

86

87

/** Proceed with original method execution */

88

public Object proceed() throws InvocationTargetException, IllegalAccessException;

89

}

90

```

91

92

### Plugin Utility Class

93

94

Utility for creating proxy objects that intercept method calls.

95

96

```java { .api }

97

/**

98

* Plugin wrapper utility for creating intercepting proxies

99

*/

100

class Plugin implements InvocationHandler {

101

/** Wrap target object with interceptor */

102

public static Object wrap(Object target, Interceptor interceptor);

103

104

/** Unwrap proxy to get original target */

105

public static Object unwrap(Object proxy);

106

107

/** Handle intercepted method invocations */

108

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

109

}

110

```

111

112

### Interceptable Objects

113

114

MyBatis provides several key interception points in the execution pipeline:

115

116

#### Executor Interception

117

118

Intercept SQL execution at the executor level.

119

120

```java { .api }

121

// Available Executor interception points:

122

// - update(MappedStatement ms, Object parameter)

123

// - query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)

124

// - query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql)

125

// - queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds)

126

// - flushStatements()

127

// - commit(boolean required)

128

// - rollback(boolean required)

129

// - getTransaction()

130

// - close(boolean forceRollback)

131

// - isClosed()

132

```

133

134

**Usage Examples:**

135

136

```java

137

@Intercepts({

138

@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),

139

@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})

140

})

141

public class ExecutorInterceptor implements Interceptor {

142

@Override

143

public Object intercept(Invocation invocation) throws Throwable {

144

Object target = invocation.getTarget();

145

Method method = invocation.getMethod();

146

Object[] args = invocation.getArgs();

147

148

// Pre-processing

149

long startTime = System.currentTimeMillis();

150

MappedStatement ms = (MappedStatement) args[0];

151

System.out.println("Executing: " + ms.getId());

152

153

try {

154

// Execute original method

155

Object result = invocation.proceed();

156

157

// Post-processing

158

long endTime = System.currentTimeMillis();

159

System.out.println("Execution time: " + (endTime - startTime) + "ms");

160

161

return result;

162

} catch (Exception e) {

163

System.out.println("Execution failed: " + e.getMessage());

164

throw e;

165

}

166

}

167

}

168

```

169

170

#### ParameterHandler Interception

171

172

Intercept parameter setting before SQL execution.

173

174

```java { .api }

175

// Available ParameterHandler interception points:

176

// - getParameterObject()

177

// - setParameters(PreparedStatement ps)

178

```

179

180

**Usage Examples:**

181

182

```java

183

@Intercepts({

184

@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})

185

})

186

public class ParameterInterceptor implements Interceptor {

187

@Override

188

public Object intercept(Invocation invocation) throws Throwable {

189

ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();

190

PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];

191

192

// Access parameter object

193

Object parameterObject = parameterHandler.getParameterObject();

194

195

// Custom parameter processing (e.g., encryption, validation)

196

if (parameterObject instanceof User) {

197

User user = (User) parameterObject;

198

if (user.getPassword() != null) {

199

user.setPassword(encryptPassword(user.getPassword()));

200

}

201

}

202

203

// Proceed with original parameter setting

204

return invocation.proceed();

205

}

206

207

private String encryptPassword(String password) {

208

// Custom encryption logic

209

return "encrypted:" + password;

210

}

211

}

212

```

213

214

#### ResultSetHandler Interception

215

216

Intercept result set processing after SQL execution.

217

218

```java { .api }

219

// Available ResultSetHandler interception points:

220

// - handleResultSets(Statement stmt)

221

// - handleCursorResultSets(Statement stmt)

222

// - handleOutputParameters(CallableStatement cs)

223

```

224

225

**Usage Examples:**

226

227

```java

228

@Intercepts({

229

@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})

230

})

231

public class ResultSetInterceptor implements Interceptor {

232

@Override

233

public Object intercept(Invocation invocation) throws Throwable {

234

// Execute original result set handling

235

Object result = invocation.proceed();

236

237

// Post-process results

238

if (result instanceof List) {

239

List<?> list = (List<?>) result;

240

System.out.println("Retrieved " + list.size() + " records");

241

242

// Custom result processing (e.g., data masking, audit logging)

243

for (Object item : list) {

244

if (item instanceof User) {

245

User user = (User) item;

246

// Mask sensitive data

247

user.setPassword("***MASKED***");

248

}

249

}

250

}

251

252

return result;

253

}

254

}

255

```

256

257

#### StatementHandler Interception

258

259

Intercept statement preparation and execution.

260

261

```java { .api }

262

// Available StatementHandler interception points:

263

// - prepare(Connection connection, Integer transactionTimeout)

264

// - parameterize(Statement statement)

265

// - batch(Statement statement)

266

// - update(Statement statement)

267

// - query(Statement statement, ResultHandler resultHandler)

268

// - queryCursor(Statement statement)

269

// - getBoundSql()

270

// - getParameterHandler()

271

```

272

273

**Usage Examples:**

274

275

```java

276

@Intercepts({

277

@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),

278

@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})

279

})

280

public class StatementInterceptor implements Interceptor {

281

@Override

282

public Object intercept(Invocation invocation) throws Throwable {

283

StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

284

String methodName = invocation.getMethod().getName();

285

286

if ("prepare".equals(methodName)) {

287

// Intercept SQL preparation

288

BoundSql boundSql = statementHandler.getBoundSql();

289

String originalSql = boundSql.getSql();

290

291

// Modify SQL if needed (e.g., add tenant filters)

292

String modifiedSql = addTenantFilter(originalSql);

293

294

// Use reflection to modify the SQL

295

Field sqlField = boundSql.getClass().getDeclaredField("sql");

296

sqlField.setAccessible(true);

297

sqlField.set(boundSql, modifiedSql);

298

}

299

300

return invocation.proceed();

301

}

302

303

private String addTenantFilter(String sql) {

304

// Add tenant filtering logic

305

if (sql.toLowerCase().contains("select") && !sql.toLowerCase().contains("tenant_id")) {

306

// Simple example - add tenant filter to WHERE clause

307

return sql + " AND tenant_id = " + getCurrentTenantId();

308

}

309

return sql;

310

}

311

312

private Long getCurrentTenantId() {

313

// Get current tenant from security context

314

return SecurityContextHolder.getTenantId();

315

}

316

}

317

```

318

319

### Common Plugin Use Cases

320

321

#### Logging Plugin

322

323

Comprehensive logging of SQL execution with timing and parameter information.

324

325

```java

326

@Intercepts({

327

@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),

328

@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})

329

})

330

public class SqlLoggingPlugin implements Interceptor {

331

private static final Logger logger = LoggerFactory.getLogger(SqlLoggingPlugin.class);

332

333

@Override

334

public Object intercept(Invocation invocation) throws Throwable {

335

MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];

336

Object parameter = invocation.getArgs()[1];

337

338

BoundSql boundSql = mappedStatement.getBoundSql(parameter);

339

String sql = boundSql.getSql();

340

341

long startTime = System.currentTimeMillis();

342

343

try {

344

Object result = invocation.proceed();

345

346

long endTime = System.currentTimeMillis();

347

long executionTime = endTime - startTime;

348

349

logger.info("SQL Executed: {} | Parameters: {} | Execution Time: {}ms",

350

sql.replaceAll("\\s+", " "), parameter, executionTime);

351

352

return result;

353

} catch (Exception e) {

354

logger.error("SQL Execution Failed: {} | Parameters: {} | Error: {}",

355

sql.replaceAll("\\s+", " "), parameter, e.getMessage());

356

throw e;

357

}

358

}

359

360

@Override

361

public void setProperties(Properties properties) {

362

// Configure logging levels, formats, etc.

363

}

364

}

365

```

366

367

#### Performance Monitoring Plugin

368

369

Monitor and alert on slow queries and performance issues.

370

371

```java

372

@Intercepts({

373

@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),

374

@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})

375

})

376

public class PerformanceMonitoringPlugin implements Interceptor {

377

private long slowQueryThreshold = 1000; // 1 second

378

private final MetricRegistry metrics = new MetricRegistry();

379

380

@Override

381

public Object intercept(Invocation invocation) throws Throwable {

382

MappedStatement ms = (MappedStatement) invocation.getArgs()[0];

383

String statementId = ms.getId();

384

385

Timer.Context context = metrics.timer(statementId).time();

386

387

try {

388

Object result = invocation.proceed();

389

390

long executionTime = context.stop() / 1_000_000; // Convert to milliseconds

391

392

if (executionTime > slowQueryThreshold) {

393

// Alert on slow query

394

alertSlowQuery(statementId, executionTime);

395

}

396

397

return result;

398

} catch (Exception e) {

399

metrics.meter(statementId + ".errors").mark();

400

throw e;

401

}

402

}

403

404

private void alertSlowQuery(String statementId, long executionTime) {

405

System.err.println("SLOW QUERY ALERT: " + statementId + " took " + executionTime + "ms");

406

// Send to monitoring system

407

}

408

409

@Override

410

public void setProperties(Properties properties) {

411

this.slowQueryThreshold = Long.parseLong(properties.getProperty("slowQueryThreshold", "1000"));

412

}

413

}

414

```

415

416

#### Security Plugin

417

418

Implement row-level security and data masking.

419

420

```java

421

@Intercepts({

422

@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})

423

})

424

public class SecurityPlugin implements Interceptor {

425

private final Set<String> sensitiveFields = Set.of("password", "ssn", "creditCard");

426

427

@Override

428

public Object intercept(Invocation invocation) throws Throwable {

429

Object result = invocation.proceed();

430

431

// Apply data masking based on user role

432

String userRole = getCurrentUserRole();

433

434

if (!"ADMIN".equals(userRole)) {

435

maskSensitiveData(result);

436

}

437

438

return result;

439

}

440

441

@SuppressWarnings("unchecked")

442

private void maskSensitiveData(Object result) {

443

if (result instanceof List) {

444

((List<Object>) result).forEach(this::maskObjectFields);

445

} else if (result != null) {

446

maskObjectFields(result);

447

}

448

}

449

450

private void maskObjectFields(Object obj) {

451

if (obj == null) return;

452

453

Class<?> clazz = obj.getClass();

454

for (Field field : clazz.getDeclaredFields()) {

455

if (sensitiveFields.contains(field.getName().toLowerCase())) {

456

field.setAccessible(true);

457

try {

458

if (field.getType() == String.class) {

459

field.set(obj, "***MASKED***");

460

}

461

} catch (IllegalAccessException e) {

462

// Log error

463

}

464

}

465

}

466

}

467

468

private String getCurrentUserRole() {

469

// Get from security context

470

return "USER";

471

}

472

}

473

```

474

475

### Plugin Configuration

476

477

#### XML Configuration

478

479

```xml

480

<!-- Configure plugins in mybatis-config.xml -->

481

<configuration>

482

<plugins>

483

<plugin interceptor="com.example.SqlLoggingPlugin">

484

<property name="logLevel" value="INFO"/>

485

<property name="includeParameters" value="true"/>

486

</plugin>

487

488

<plugin interceptor="com.example.PerformanceMonitoringPlugin">

489

<property name="slowQueryThreshold" value="2000"/>

490

<property name="enableMetrics" value="true"/>

491

</plugin>

492

493

<plugin interceptor="com.example.SecurityPlugin">

494

<property name="maskingEnabled" value="true"/>

495

<property name="adminRoles" value="ADMIN,SUPER_ADMIN"/>

496

</plugin>

497

</plugins>

498

</configuration>

499

```

500

501

#### Programmatic Configuration

502

503

```java

504

// Add plugins programmatically

505

Configuration configuration = new Configuration();

506

507

// Add logging plugin

508

SqlLoggingPlugin loggingPlugin = new SqlLoggingPlugin();

509

Properties loggingProps = new Properties();

510

loggingProps.setProperty("logLevel", "DEBUG");

511

loggingPlugin.setProperties(loggingProps);

512

configuration.addInterceptor(loggingPlugin);

513

514

// Add performance monitoring plugin

515

PerformanceMonitoringPlugin perfPlugin = new PerformanceMonitoringPlugin();

516

Properties perfProps = new Properties();

517

perfProps.setProperty("slowQueryThreshold", "1500");

518

perfPlugin.setProperties(perfProps);

519

configuration.addInterceptor(perfPlugin);

520

521

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(configuration);

522

```

523

524

## Types

525

526

```java { .api }

527

/**

528

* Plugin chain for managing multiple interceptors

529

*/

530

class InterceptorChain {

531

/** Add interceptor to chain */

532

public void addInterceptor(Interceptor interceptor);

533

534

/** Apply all interceptors to target object */

535

public Object pluginAll(Object target);

536

537

/** Get all interceptors */

538

public List<Interceptor> getInterceptors();

539

}

540

541

/**

542

* Plugin-related exceptions

543

*/

544

class PluginException extends PersistenceException {

545

public PluginException(String message);

546

public PluginException(String message, Throwable cause);

547

}

548

549

/**

550

* Metadata about intercepted methods

551

*/

552

class InterceptorMetadata {

553

/** Get target class */

554

public Class<?> getTargetClass();

555

556

/** Get intercepted methods */

557

public Set<Method> getInterceptedMethods();

558

559

/** Check if method is intercepted */

560

public boolean isMethodIntercepted(Method method);

561

}

562

```