or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mddeclarative-transactions.mdindex.mdprogrammatic-transactions.mdtransaction-semantics.md

declarative-transactions.mddocs/

0

# Declarative Transaction Management

1

2

Standard JTA @Transactional annotations with Quarkus-specific enhancements for timeout configuration and comprehensive CDI integration.

3

4

## Capabilities

5

6

### @Transactional Annotation

7

8

Standard Jakarta Transaction annotation for declarative transaction management.

9

10

```java { .api }

11

/**

12

* Marks method or class for automatic transaction management

13

*/

14

@Target({ElementType.TYPE, ElementType.METHOD})

15

@Retention(RetentionPolicy.RUNTIME)

16

@Inherited

17

@interface Transactional {

18

19

/**

20

* Transaction propagation behavior

21

* @return TxType constant defining transaction semantics

22

*/

23

TxType value() default TxType.REQUIRED;

24

25

/**

26

* Exception types that trigger rollback

27

* @return Array of exception classes

28

*/

29

Class[] rollbackOn() default {};

30

31

/**

32

* Exception types that do NOT trigger rollback

33

* @return Array of exception classes

34

*/

35

Class[] dontRollbackOn() default {};

36

}

37

```

38

39

**TxType Values:**

40

41

```java { .api }

42

enum TxType {

43

/** Join existing transaction or create new one (default) */

44

REQUIRED,

45

46

/** Always create new transaction, suspend existing */

47

REQUIRES_NEW,

48

49

/** Must run within existing transaction, throw exception if none */

50

MANDATORY,

51

52

/** Run within transaction if exists, without transaction if none */

53

SUPPORTS,

54

55

/** Always run without transaction, suspend existing */

56

NOT_SUPPORTED,

57

58

/** Throw exception if transaction exists */

59

NEVER

60

}

61

```

62

63

**Usage Examples:**

64

65

```java

66

import jakarta.transaction.Transactional;

67

import static jakarta.transaction.Transactional.TxType.*;

68

69

@ApplicationScoped

70

public class UserService {

71

72

@Transactional // Uses REQUIRED by default

73

public void createUser(User user) {

74

validateUser(user);

75

userRepository.persist(user);

76

// Transaction committed automatically on success

77

// Rolled back automatically on RuntimeException

78

}

79

80

@Transactional(REQUIRES_NEW)

81

public void auditUserCreation(String username) {

82

// Always runs in new transaction, independent of caller

83

auditRepository.log("User created: " + username);

84

}

85

86

@Transactional(MANDATORY)

87

public void updateUserInTransaction(User user) {

88

// Must be called within existing transaction

89

user.setLastModified(Instant.now());

90

userRepository.merge(user);

91

}

92

93

@Transactional(NOT_SUPPORTED)

94

public void sendEmailNotification(String email, String message) {

95

// Runs outside transaction scope

96

emailService.send(email, message);

97

}

98

}

99

```

100

101

### Exception Handling in Declarative Transactions

102

103

Configure which exceptions cause rollback vs commit.

104

105

```java { .api }

106

/**

107

* Control transaction rollback based on exception types

108

*/

109

@Transactional(

110

rollbackOn = {BusinessException.class, ValidationException.class},

111

dontRollbackOn = {WarningException.class}

112

)

113

```

114

115

**Usage Examples:**

116

117

```java

118

@ApplicationScoped

119

public class PaymentService {

120

121

// Roll back on any RuntimeException (default behavior)

122

@Transactional

123

public void processPayment(Payment payment) {

124

if (payment.getAmount().compareTo(BigDecimal.ZERO) <= 0) {

125

throw new IllegalArgumentException("Invalid amount"); // Triggers rollback

126

}

127

paymentProcessor.charge(payment);

128

}

129

130

// Custom rollback behavior

131

@Transactional(

132

rollbackOn = {InsufficientFundsException.class},

133

dontRollbackOn = {PaymentWarningException.class}

134

)

135

public void processRiskyPayment(Payment payment) {

136

try {

137

paymentProcessor.processHighRisk(payment);

138

} catch (PaymentWarningException e) {

139

// Transaction continues and commits

140

logger.warn("Payment processed with warning", e);

141

} catch (InsufficientFundsException e) {

142

// Transaction is rolled back

143

throw e;

144

}

145

}

146

}

147

```

148

149

### @TransactionConfiguration Annotation

150

151

Quarkus-specific annotation for configuring transaction timeouts.

152

153

```java { .api }

154

/**

155

* Configure transaction timeout at method or class level

156

*/

157

@Target({ElementType.METHOD, ElementType.TYPE})

158

@Retention(RetentionPolicy.RUNTIME)

159

@Inherited

160

@interface TransactionConfiguration {

161

162

/** Indicates no timeout configured */

163

int UNSET_TIMEOUT = -1;

164

165

/** Indicates no config property configured */

166

String UNSET_TIMEOUT_CONFIG_PROPERTY = "<<unset>>";

167

168

/**

169

* Transaction timeout in seconds

170

* @return Timeout value, UNSET_TIMEOUT for default

171

*/

172

int timeout() default UNSET_TIMEOUT;

173

174

/**

175

* Configuration property name for timeout value

176

* Property value takes precedence over timeout() if both are set

177

* @return Property name or UNSET_TIMEOUT_CONFIG_PROPERTY

178

*/

179

String timeoutFromConfigProperty() default UNSET_TIMEOUT_CONFIG_PROPERTY;

180

}

181

```

182

183

**Usage Examples:**

184

185

```java

186

@ApplicationScoped

187

public class BatchProcessingService {

188

189

@Transactional

190

@TransactionConfiguration(timeout = 300) // 5 minutes

191

public void processLargeBatch(List<BatchItem> items) {

192

for (BatchItem item : items) {

193

processItem(item);

194

}

195

}

196

197

@Transactional

198

@TransactionConfiguration(timeoutFromConfigProperty = "batch.processing.timeout")

199

public void configurableBatchProcess(List<BatchItem> items) {

200

// Timeout read from application.properties: batch.processing.timeout=600

201

processBatchItems(items);

202

}

203

204

@Transactional

205

@TransactionConfiguration(

206

timeout = 120, // Fallback value

207

timeoutFromConfigProperty = "critical.operation.timeout"

208

)

209

public void criticalOperation() {

210

// Uses property value if set, otherwise uses 120 seconds

211

performCriticalWork();

212

}

213

}

214

```

215

216

### Class-Level Configuration

217

218

Apply transaction behavior to all methods in a class.

219

220

```java { .api }

221

/**

222

* Class-level annotations apply to all @Transactional methods

223

* Method-level configuration overrides class-level

224

*/

225

@Transactional(REQUIRES_NEW)

226

@TransactionConfiguration(timeout = 60)

227

@ApplicationScoped

228

public class AuditService {

229

230

// Inherits REQUIRES_NEW and 60s timeout

231

@Transactional

232

public void logUserAction(String action) { }

233

234

// Overrides to use REQUIRED semantics, keeps 60s timeout

235

@Transactional(REQUIRED)

236

public void logSystemEvent(String event) { }

237

238

// Overrides timeout to 30s, keeps REQUIRES_NEW semantics

239

@TransactionConfiguration(timeout = 30)

240

@Transactional

241

public void logQuickEvent(String event) { }

242

}

243

```

244

245

### CDI Integration Patterns

246

247

Integration with CDI scopes and lifecycle events.

248

249

```java { .api }

250

/**

251

* Transaction-scoped CDI beans

252

*/

253

@TransactionScoped

254

public class TransactionScopedAuditLogger {

255

256

private List<String> transactionLogs = new ArrayList<>();

257

258

@PostConstruct

259

void onTransactionBegin() {

260

// Called when transaction begins

261

transactionLogs.add("Transaction started at " + Instant.now());

262

}

263

264

@PreDestroy

265

void onTransactionEnd() {

266

// Called before transaction ends (commit or rollback)

267

persistLogs(transactionLogs);

268

}

269

270

public void log(String message) {

271

transactionLogs.add(message);

272

}

273

}

274

```

275

276

**Transaction Lifecycle Events:**

277

278

```java

279

@ApplicationScoped

280

public class TransactionEventObserver {

281

282

void onTransactionBegin(@Observes @Initialized(TransactionScoped.class) Object event) {

283

logger.info("Transaction scope initialized");

284

}

285

286

void onBeforeTransactionEnd(@Observes @BeforeDestroyed(TransactionScoped.class) Object event) {

287

logger.info("Transaction about to end");

288

}

289

290

void onTransactionEnd(@Observes @Destroyed(TransactionScoped.class) Object event) {

291

logger.info("Transaction scope destroyed");

292

}

293

}

294

```

295

296

### Interceptor Behavior

297

298

Understanding how transaction interceptors work with method calls.

299

300

```java

301

@ApplicationScoped

302

public class OrderService {

303

304

@Transactional

305

public void processOrder(Order order) {

306

validateOrder(order); // Runs in same transaction

307

persistOrder(order); // Runs in same transaction

308

notifyCustomer(order); // Runs in same transaction

309

}

310

311

@Transactional(REQUIRES_NEW)

312

private void auditOrderProcessing(Order order) {

313

// Private method - interceptor NOT applied!

314

// This will NOT start a new transaction

315

auditRepository.log("Processing order: " + order.getId());

316

}

317

318

@Transactional(REQUIRES_NEW)

319

public void auditOrderProcessingPublic(Order order) {

320

// Public method - interceptor applied

321

// This WILL start a new transaction

322

auditRepository.log("Processing order: " + order.getId());

323

}

324

}

325

```

326

327

## Best Practices

328

329

### Method Visibility

330

331

```java

332

// ✅ Correct - public method, interceptor applies

333

@Transactional

334

public void processData() { }

335

336

// ❌ Wrong - private method, interceptor NOT applied

337

@Transactional

338

private void processDataPrivate() { }

339

340

// ✅ Correct - package-private works with CDI

341

@Transactional

342

void processDataPackage() { }

343

```

344

345

### Self-Invocation Issues

346

347

```java

348

@ApplicationScoped

349

public class DocumentService {

350

351

@Transactional

352

public void processDocument(Document doc) {

353

validateDocument(doc);

354

// ❌ Self-invocation - transaction interceptor NOT applied

355

this.saveDocument(doc);

356

}

357

358

@Transactional(REQUIRES_NEW)

359

public void saveDocument(Document doc) {

360

// This will NOT start new transaction when called from processDocument

361

documentRepository.save(doc);

362

}

363

}

364

365

// ✅ Solution: Inject self or use separate service

366

@ApplicationScoped

367

public class DocumentService {

368

369

@Inject

370

DocumentService self; // CDI proxy

371

372

@Transactional

373

public void processDocument(Document doc) {

374

validateDocument(doc);

375

// ✅ Correct - uses CDI proxy, interceptor applies

376

self.saveDocument(doc);

377

}

378

}

379

```

380

381

### Exception Handling Best Practices

382

383

```java

384

@Transactional

385

public void businessOperation() {

386

try {

387

riskyOperation();

388

} catch (CheckedException e) {

389

// Checked exceptions don't trigger rollback by default

390

// Convert to runtime exception to trigger rollback

391

throw new BusinessException("Operation failed", e);

392

}

393

}

394

395

@Transactional(rollbackOn = CheckedException.class)

396

public void businessOperationWithCheckedRollback() {

397

// Now CheckedException will trigger rollback

398

riskyOperationThatThrowsCheckedException();

399

}

400

```