0
# Data Access & Transaction Management
1
2
Spring provides comprehensive data access support including JDBC abstraction, transaction management, and integration with ORM frameworks like Hibernate and JPA. This includes spring-jdbc, spring-tx, spring-orm, and spring-r2dbc modules.
3
4
## Maven Dependencies
5
6
```xml
7
<!-- Spring JDBC -->
8
<dependency>
9
<groupId>org.springframework</groupId>
10
<artifactId>spring-jdbc</artifactId>
11
<version>5.3.39</version>
12
</dependency>
13
14
<!-- Spring Transaction Management -->
15
<dependency>
16
<groupId>org.springframework</groupId>
17
<artifactId>spring-tx</artifactId>
18
<version>5.3.39</version>
19
</dependency>
20
21
<!-- Spring ORM -->
22
<dependency>
23
<groupId>org.springframework</groupId>
24
<artifactId>spring-orm</artifactId>
25
<version>5.3.39</version>
26
</dependency>
27
28
<!-- Spring R2DBC (Reactive) -->
29
<dependency>
30
<groupId>org.springframework</groupId>
31
<artifactId>spring-r2dbc</artifactId>
32
<version>5.3.39</version>
33
</dependency>
34
35
<!-- Database driver (example: H2) -->
36
<dependency>
37
<groupId>com.h2database</groupId>
38
<artifactId>h2</artifactId>
39
<version>2.1.214</version>
40
</dependency>
41
```
42
43
## Core Imports
44
45
```java { .api }
46
// JDBC Core
47
import org.springframework.jdbc.core.JdbcTemplate;
48
import org.springframework.jdbc.core.NamedParameterJdbcTemplate;
49
import org.springframework.jdbc.core.RowMapper;
50
import org.springframework.jdbc.core.ResultSetExtractor;
51
import org.springframework.jdbc.core.PreparedStatementCreator;
52
import org.springframework.jdbc.core.PreparedStatementSetter;
53
54
// Data Source
55
import javax.sql.DataSource;
56
import org.springframework.jdbc.datasource.DriverManagerDataSource;
57
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
58
59
// Transaction Management
60
import org.springframework.transaction.PlatformTransactionManager;
61
import org.springframework.transaction.TransactionDefinition;
62
import org.springframework.transaction.TransactionStatus;
63
import org.springframework.transaction.support.TransactionTemplate;
64
import org.springframework.transaction.annotation.Transactional;
65
import org.springframework.transaction.annotation.EnableTransactionManagement;
66
67
// ORM Integration
68
import org.springframework.orm.jpa.JpaTransactionManager;
69
import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;
70
import org.springframework.orm.hibernate5.HibernateTransactionManager;
71
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
72
73
// Exception Translation
74
import org.springframework.dao.DataAccessException;
75
import org.springframework.dao.EmptyResultDataAccessException;
76
import org.springframework.dao.DuplicateKeyException;
77
78
// R2DBC (Reactive)
79
import org.springframework.r2dbc.core.DatabaseClient;
80
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
81
```
82
83
## JDBC Support
84
85
### JdbcTemplate
86
87
```java { .api }
88
// Central class in JDBC core package providing JDBC operations
89
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
90
91
// Query methods
92
public <T> T queryForObject(String sql, Class<T> requiredType);
93
public <T> T queryForObject(String sql, Class<T> requiredType, Object... args);
94
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args);
95
public <T> List<T> query(String sql, RowMapper<T> rowMapper);
96
public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args);
97
public <T> List<T> query(String sql, PreparedStatementSetter pss, RowMapper<T> rowMapper);
98
public <T> T query(String sql, ResultSetExtractor<T> rse);
99
100
// Update methods
101
public int update(String sql);
102
public int update(String sql, Object... args);
103
public int update(String sql, PreparedStatementSetter pss);
104
public int update(PreparedStatementCreator psc);
105
106
// Batch operations
107
public int[] batchUpdate(String sql, List<Object[]> batchArgs);
108
public int[] batchUpdate(String sql, BatchPreparedStatementSetter pss);
109
110
// Execute methods
111
public void execute(String sql);
112
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action);
113
}
114
115
// RowMapper interface for mapping rows of ResultSet
116
@FunctionalInterface
117
public interface RowMapper<T> {
118
T mapRow(ResultSet rs, int rowNum) throws SQLException;
119
}
120
121
// Interface used by JdbcTemplate for extracting values from ResultSet
122
@FunctionalInterface
123
public interface ResultSetExtractor<T> {
124
T extractData(ResultSet rs) throws SQLException, DataAccessException;
125
}
126
127
// Interface for setting values on PreparedStatement
128
@FunctionalInterface
129
public interface PreparedStatementSetter {
130
void setValues(PreparedStatement ps) throws SQLException;
131
}
132
```
133
134
### NamedParameterJdbcTemplate
135
136
```java { .api }
137
// Template class with support for named parameters
138
public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations {
139
140
public NamedParameterJdbcTemplate(DataSource dataSource);
141
public NamedParameterJdbcTemplate(JdbcOperations classicJdbcTemplate);
142
143
// Query methods with named parameters
144
public <T> T queryForObject(String sql, Map<String, ?> paramMap, Class<T> requiredType);
145
public <T> T queryForObject(String sql, SqlParameterSource paramSource, Class<T> requiredType);
146
public <T> T queryForObject(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper);
147
public <T> List<T> query(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper);
148
public <T> List<T> query(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper);
149
150
// Update methods with named parameters
151
public int update(String sql, Map<String, ?> paramMap);
152
public int update(String sql, SqlParameterSource paramSource);
153
154
// Batch operations
155
public int[] batchUpdate(String sql, Map<String, ?>[] batchValues);
156
public int[] batchUpdate(String sql, SqlParameterSource[] batchArgs);
157
}
158
159
// Interface for parameter sources
160
public interface SqlParameterSource {
161
boolean hasValue(String paramName);
162
Object getValue(String paramName) throws IllegalArgumentException;
163
int getSqlType(String paramName);
164
String getTypeName(String paramName);
165
String[] getParameterNames();
166
}
167
168
// Map-based parameter source
169
public class MapSqlParameterSource implements SqlParameterSource {
170
public MapSqlParameterSource();
171
public MapSqlParameterSource(String paramName, Object value);
172
public MapSqlParameterSource(Map<String, ?> values);
173
174
public MapSqlParameterSource addValue(String paramName, Object value);
175
public MapSqlParameterSource addValue(String paramName, Object value, int sqlType);
176
public MapSqlParameterSource addValues(Map<String, ?> values);
177
}
178
```
179
180
## Transaction Management
181
182
### Core Transaction Interfaces
183
184
```java { .api }
185
// Central interface for transaction management
186
public interface PlatformTransactionManager {
187
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
188
void commit(TransactionStatus status) throws TransactionException;
189
void rollback(TransactionStatus status) throws TransactionException;
190
}
191
192
// Interface that defines Spring-compliant transaction properties
193
public interface TransactionDefinition {
194
int PROPAGATION_REQUIRED = 0;
195
int PROPAGATION_SUPPORTS = 1;
196
int PROPAGATION_MANDATORY = 2;
197
int PROPAGATION_REQUIRES_NEW = 3;
198
int PROPAGATION_NOT_SUPPORTED = 4;
199
int PROPAGATION_NEVER = 5;
200
int PROPAGATION_NESTED = 6;
201
202
int ISOLATION_DEFAULT = -1;
203
int ISOLATION_READ_UNCOMMITTED = 1;
204
int ISOLATION_READ_COMMITTED = 2;
205
int ISOLATION_REPEATABLE_READ = 4;
206
int ISOLATION_SERIALIZABLE = 8;
207
208
int TIMEOUT_DEFAULT = -1;
209
210
int getPropagationBehavior();
211
int getIsolationLevel();
212
int getTimeout();
213
boolean isReadOnly();
214
String getName();
215
}
216
217
// Representation of status of transaction
218
public interface TransactionStatus extends SavepointManager, Flushable {
219
boolean isNewTransaction();
220
boolean hasSavepoint();
221
void setRollbackOnly();
222
boolean isRollbackOnly();
223
boolean isCompleted();
224
}
225
226
// Template class that simplifies programmatic transaction demarcation
227
public class TransactionTemplate extends DefaultTransactionDefinition implements TransactionOperations, InitializingBean {
228
229
public TransactionTemplate();
230
public TransactionTemplate(PlatformTransactionManager transactionManager);
231
232
public <T> T execute(TransactionCallback<T> action) throws TransactionException;
233
public void execute(TransactionCallbackWithoutResult action) throws TransactionException;
234
}
235
```
236
237
### Declarative Transactions
238
239
```java { .api }
240
// Describes transaction attributes on individual methods or classes
241
@Target({ElementType.METHOD, ElementType.TYPE})
242
@Retention(RetentionPolicy.RUNTIME)
243
@Inherited
244
@Documented
245
public @interface Transactional {
246
247
@AliasFor("transactionManager")
248
String value() default "";
249
250
String transactionManager() default "";
251
252
Propagation propagation() default Propagation.REQUIRED;
253
254
Isolation isolation() default Isolation.DEFAULT;
255
256
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
257
258
boolean readOnly() default false;
259
260
Class<? extends Throwable>[] rollbackFor() default {};
261
262
String[] rollbackForClassName() default {};
263
264
Class<? extends Throwable>[] noRollbackFor() default {};
265
266
String[] noRollbackForClassName() default {};
267
}
268
269
// Enables Spring's annotation-driven transaction management capability
270
@Target(ElementType.TYPE)
271
@Retention(RetentionPolicy.RUNTIME)
272
@Documented
273
@Import(TransactionManagementConfigurationSelector.class)
274
public @interface EnableTransactionManagement {
275
boolean proxyTargetClass() default false;
276
AdviceMode mode() default AdviceMode.PROXY;
277
int order() default Ordered.LOWEST_PRECEDENCE;
278
}
279
280
// Propagation enum
281
public enum Propagation {
282
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
283
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
284
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
285
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
286
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
287
NEVER(TransactionDefinition.PROPAGATION_NEVER),
288
NESTED(TransactionDefinition.PROPAGATION_NESTED);
289
}
290
291
// Isolation enum
292
public enum Isolation {
293
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
294
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
295
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
296
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
297
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
298
}
299
```
300
301
### Transaction Managers
302
303
```java { .api }
304
// PlatformTransactionManager implementation for single JDBC DataSource
305
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
306
implements ResourceTransactionManager, InitializingBean {
307
308
public DataSourceTransactionManager();
309
public DataSourceTransactionManager(DataSource dataSource);
310
311
public void setDataSource(DataSource dataSource);
312
public DataSource getDataSource();
313
public void setEnforceReadOnly(boolean enforceReadOnly);
314
}
315
316
// PlatformTransactionManager for JPA EntityManagerFactory
317
public class JpaTransactionManager extends AbstractPlatformTransactionManager
318
implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {
319
320
public JpaTransactionManager();
321
public JpaTransactionManager(EntityManagerFactory emf);
322
323
public void setEntityManagerFactory(EntityManagerFactory emf);
324
public EntityManagerFactory getEntityManagerFactory();
325
public void setDataSource(DataSource dataSource);
326
}
327
328
// PlatformTransactionManager for Hibernate SessionFactory
329
public class HibernateTransactionManager extends AbstractPlatformTransactionManager
330
implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {
331
332
public HibernateTransactionManager();
333
public HibernateTransactionManager(SessionFactory sessionFactory);
334
335
public void setSessionFactory(SessionFactory sessionFactory);
336
public SessionFactory getSessionFactory();
337
public void setDataSource(DataSource dataSource);
338
}
339
```
340
341
## ORM Integration
342
343
### JPA Integration
344
345
```java { .api }
346
// FactoryBean for creating JPA EntityManagerFactory
347
public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryBean {
348
349
public void setPersistenceUnitName(String persistenceUnitName);
350
public void setPersistenceUnitPostProcessors(PersistenceUnitPostProcessor... postProcessors);
351
352
@Override
353
protected EntityManagerFactory createNativeEntityManagerFactory() throws PersistenceException;
354
}
355
356
// Extended EntityManagerFactory interface for container integration
357
public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManagerFactoryBean
358
implements ResourceLoaderAware, LoadTimeWeaverAware {
359
360
public void setDataSource(DataSource dataSource);
361
public void setPackagesToScan(String... packagesToScan);
362
public void setJpaVendorAdapter(JpaVendorAdapter jpaVendorAdapter);
363
public void setJpaProperties(Properties jpaProperties);
364
}
365
366
// JPA Vendor Adapter for Hibernate
367
public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter {
368
public void setDatabase(Database database);
369
public void setDatabasePlatform(String databasePlatform);
370
public void setGenerateDdl(boolean generateDdl);
371
public void setShowSql(boolean showSql);
372
}
373
```
374
375
### Hibernate Integration
376
377
```java { .api }
378
// FactoryBean for creating Hibernate SessionFactory
379
public class LocalSessionFactoryBean extends HibernateExceptionTranslator
380
implements FactoryBean<SessionFactory>, ResourceLoaderAware, InitializingBean, DisposableBean {
381
382
public void setDataSource(DataSource dataSource);
383
public void setPackagesToScan(String... packagesToScan);
384
public void setAnnotatedClasses(Class<?>... annotatedClasses);
385
public void setHibernateProperties(Properties hibernateProperties);
386
public void setConfigLocation(Resource configLocation);
387
}
388
389
// Helper class for Hibernate data access (legacy approach)
390
public class HibernateTemplate extends HibernateAccessor implements HibernateOperations {
391
392
public HibernateTemplate();
393
public HibernateTemplate(SessionFactory sessionFactory);
394
395
// CRUD operations
396
public void save(Object entity);
397
public void update(Object entity);
398
public void saveOrUpdate(Object entity);
399
public void delete(Object entity);
400
public <T> T get(Class<T> entityClass, Serializable id);
401
public <T> T load(Class<T> entityClass, Serializable id);
402
403
// Query operations
404
public List<?> find(String queryString, Object... values);
405
public <T> List<T> findByNamedParam(String queryString, String[] paramNames, Object[] values);
406
}
407
```
408
409
## Reactive Data Access (R2DBC)
410
411
### R2DBC Support
412
413
```java { .api }
414
// Configuration base class for R2DBC
415
public abstract class AbstractR2dbcConfiguration {
416
417
@Bean
418
public abstract ConnectionFactory connectionFactory();
419
420
@Bean
421
public R2dbcTransactionManager transactionManager() {
422
return new R2dbcTransactionManager(connectionFactory());
423
}
424
425
@Bean
426
public DatabaseClient databaseClient() {
427
return DatabaseClient.builder()
428
.connectionFactory(connectionFactory())
429
.build();
430
}
431
}
432
433
// Reactive database client
434
public interface DatabaseClient {
435
436
GenericExecuteSpec sql(String sql);
437
GenericExecuteSpec sql(Supplier<String> sqlSupplier);
438
439
// Execute spec for generic SQL operations
440
interface GenericExecuteSpec {
441
GenericExecuteSpec bind(Object identifier, Object value);
442
GenericExecuteSpec bind(int index, Object value);
443
GenericExecuteSpec bindNull(Object identifier, Class<?> type);
444
445
FetchSpec<Map<String, Object>> fetch();
446
<T> FetchSpec<T> map(Function<Row, T> mappingFunction);
447
<T> FetchSpec<T> map(BiFunction<Row, RowMetadata, T> mappingFunction);
448
449
Mono<Void> then();
450
}
451
452
// Fetch specification
453
interface FetchSpec<T> {
454
Mono<T> one();
455
Mono<T> first();
456
Flux<T> all();
457
Mono<Long> rowsUpdated();
458
}
459
}
460
461
// R2DBC Transaction Manager
462
public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
463
implements ResourceTransactionManager, InitializingBean {
464
465
public R2dbcTransactionManager(ConnectionFactory connectionFactory);
466
public void setConnectionFactory(ConnectionFactory connectionFactory);
467
public ConnectionFactory getConnectionFactory();
468
}
469
```
470
471
## Practical Usage Examples
472
473
### Basic JDBC Operations
474
475
```java { .api }
476
@Repository
477
public class UserRepository {
478
479
private final JdbcTemplate jdbcTemplate;
480
481
public UserRepository(JdbcTemplate jdbcTemplate) {
482
this.jdbcTemplate = jdbcTemplate;
483
}
484
485
// Simple query for object
486
public User findById(Long id) {
487
String sql = "SELECT id, username, email, created_date FROM users WHERE id = ?";
488
return jdbcTemplate.queryForObject(sql, new UserRowMapper(), id);
489
}
490
491
// Query for list
492
public List<User> findByEmail(String email) {
493
String sql = "SELECT id, username, email, created_date FROM users WHERE email LIKE ?";
494
return jdbcTemplate.query(sql, new UserRowMapper(), "%" + email + "%");
495
}
496
497
// Insert operation
498
public void save(User user) {
499
String sql = "INSERT INTO users (username, email, created_date) VALUES (?, ?, ?)";
500
jdbcTemplate.update(sql, user.getUsername(), user.getEmail(), user.getCreatedDate());
501
}
502
503
// Update operation
504
public void update(User user) {
505
String sql = "UPDATE users SET username = ?, email = ? WHERE id = ?";
506
int rowsAffected = jdbcTemplate.update(sql, user.getUsername(), user.getEmail(), user.getId());
507
508
if (rowsAffected == 0) {
509
throw new EmptyResultDataAccessException("User not found: " + user.getId(), 1);
510
}
511
}
512
513
// Delete operation
514
public void delete(Long id) {
515
String sql = "DELETE FROM users WHERE id = ?";
516
jdbcTemplate.update(sql, id);
517
}
518
519
// Custom RowMapper
520
private static class UserRowMapper implements RowMapper<User> {
521
@Override
522
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
523
User user = new User();
524
user.setId(rs.getLong("id"));
525
user.setUsername(rs.getString("username"));
526
user.setEmail(rs.getString("email"));
527
user.setCreatedDate(rs.getTimestamp("created_date").toLocalDateTime());
528
return user;
529
}
530
}
531
}
532
```
533
534
### Named Parameter Operations
535
536
```java { .api }
537
@Repository
538
public class OrderRepository {
539
540
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
541
542
public OrderRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
543
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
544
}
545
546
// Named parameters with Map
547
public List<Order> findByUserId(Long userId) {
548
String sql = "SELECT id, user_id, total_amount, order_date FROM orders WHERE user_id = :userId";
549
550
Map<String, Object> params = new HashMap<>();
551
params.put("userId", userId);
552
553
return namedParameterJdbcTemplate.query(sql, params, new OrderRowMapper());
554
}
555
556
// Named parameters with MapSqlParameterSource
557
public void save(Order order) {
558
String sql = "INSERT INTO orders (user_id, total_amount, order_date) VALUES (:userId, :totalAmount, :orderDate)";
559
560
MapSqlParameterSource params = new MapSqlParameterSource()
561
.addValue("userId", order.getUserId())
562
.addValue("totalAmount", order.getTotalAmount())
563
.addValue("orderDate", order.getOrderDate());
564
565
namedParameterJdbcTemplate.update(sql, params);
566
}
567
568
// Batch operations
569
public void saveBatch(List<Order> orders) {
570
String sql = "INSERT INTO orders (user_id, total_amount, order_date) VALUES (:userId, :totalAmount, :orderDate)";
571
572
SqlParameterSource[] batchParams = orders.stream()
573
.map(order -> new MapSqlParameterSource()
574
.addValue("userId", order.getUserId())
575
.addValue("totalAmount", order.getTotalAmount())
576
.addValue("orderDate", order.getOrderDate()))
577
.toArray(SqlParameterSource[]::new);
578
579
namedParameterJdbcTemplate.batchUpdate(sql, batchParams);
580
}
581
582
// Complex queries with multiple parameters
583
public List<Order> findOrdersByDateRange(LocalDateTime startDate, LocalDateTime endDate, OrderStatus status) {
584
String sql = """
585
SELECT id, user_id, total_amount, order_date, status
586
FROM orders
587
WHERE order_date BETWEEN :startDate AND :endDate
588
AND status = :status
589
ORDER BY order_date DESC
590
""";
591
592
MapSqlParameterSource params = new MapSqlParameterSource()
593
.addValue("startDate", startDate)
594
.addValue("endDate", endDate)
595
.addValue("status", status.name());
596
597
return namedParameterJdbcTemplate.query(sql, params, new OrderRowMapper());
598
}
599
}
600
```
601
602
### Transaction Management
603
604
```java { .api }
605
// Configuration
606
@Configuration
607
@EnableTransactionManagement
608
public class DatabaseConfig {
609
610
@Bean
611
public DataSource dataSource() {
612
HikariDataSource dataSource = new HikariDataSource();
613
dataSource.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
614
dataSource.setUsername("user");
615
dataSource.setPassword("password");
616
dataSource.setMaximumPoolSize(20);
617
return dataSource;
618
}
619
620
@Bean
621
public PlatformTransactionManager transactionManager(DataSource dataSource) {
622
return new DataSourceTransactionManager(dataSource);
623
}
624
625
@Bean
626
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
627
return new JdbcTemplate(dataSource);
628
}
629
}
630
631
// Service with declarative transactions
632
@Service
633
public class OrderService {
634
635
private final OrderRepository orderRepository;
636
private final PaymentService paymentService;
637
private final EmailService emailService;
638
639
public OrderService(OrderRepository orderRepository, PaymentService paymentService, EmailService emailService) {
640
this.orderRepository = orderRepository;
641
this.paymentService = paymentService;
642
this.emailService = emailService;
643
}
644
645
@Transactional
646
public Order createOrder(Order order) {
647
// Save order
648
Order savedOrder = orderRepository.save(order);
649
650
// Process payment
651
Payment payment = paymentService.processPayment(savedOrder);
652
653
// Send confirmation email
654
emailService.sendOrderConfirmation(savedOrder);
655
656
return savedOrder;
657
}
658
659
@Transactional(readOnly = true)
660
public List<Order> getUserOrders(Long userId) {
661
return orderRepository.findByUserId(userId);
662
}
663
664
@Transactional(propagation = Propagation.REQUIRES_NEW)
665
public void cancelOrder(Long orderId) {
666
Order order = orderRepository.findById(orderId);
667
668
if (order.getStatus() == OrderStatus.CONFIRMED) {
669
paymentService.refundPayment(order.getPaymentId());
670
order.setStatus(OrderStatus.CANCELLED);
671
orderRepository.update(order);
672
673
emailService.sendCancellationNotification(order);
674
}
675
}
676
677
@Transactional(rollbackFor = {PaymentException.class, EmailException.class})
678
public void processOrderWithStrictRollback(Order order) {
679
orderRepository.save(order);
680
paymentService.processPayment(order); // Throws PaymentException
681
emailService.sendConfirmation(order); // Throws EmailException
682
}
683
684
// Custom transaction attributes
685
@Transactional(
686
isolation = Isolation.REPEATABLE_READ,
687
timeout = 30,
688
rollbackFor = Exception.class
689
)
690
public void complexOrderProcessing(Order order) {
691
// Complex business logic with specific transaction requirements
692
}
693
}
694
```
695
696
### Programmatic Transactions
697
698
```java { .api }
699
@Service
700
public class AccountService {
701
702
private final AccountRepository accountRepository;
703
private final TransactionTemplate transactionTemplate;
704
705
public AccountService(AccountRepository accountRepository, PlatformTransactionManager transactionManager) {
706
this.accountRepository = accountRepository;
707
this.transactionTemplate = new TransactionTemplate(transactionManager);
708
}
709
710
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
711
transactionTemplate.execute(status -> {
712
try {
713
Account fromAccount = accountRepository.findById(fromAccountId);
714
Account toAccount = accountRepository.findById(toAccountId);
715
716
if (fromAccount.getBalance().compareTo(amount) < 0) {
717
throw new InsufficientFundsException("Not enough funds");
718
}
719
720
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
721
toAccount.setBalance(toAccount.getBalance().add(amount));
722
723
accountRepository.update(fromAccount);
724
accountRepository.update(toAccount);
725
726
return null; // TransactionCallbackWithoutResult
727
728
} catch (Exception e) {
729
status.setRollbackOnly();
730
throw e;
731
}
732
});
733
}
734
735
// With return value
736
public Account createAccountWithInitialDeposit(String accountNumber, BigDecimal initialDeposit) {
737
return transactionTemplate.execute(status -> {
738
Account account = new Account();
739
account.setAccountNumber(accountNumber);
740
account.setBalance(initialDeposit);
741
742
Long accountId = accountRepository.save(account);
743
account.setId(accountId);
744
745
// Log transaction
746
auditService.logAccountCreation(account);
747
748
return account;
749
});
750
}
751
752
// Custom transaction definition
753
public void performOperationWithCustomTransaction() {
754
DefaultTransactionDefinition txDef = new DefaultTransactionDefinition();
755
txDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
756
txDef.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
757
txDef.setTimeout(30);
758
759
TransactionTemplate customTemplate = new TransactionTemplate(transactionManager, txDef);
760
761
customTemplate.execute(status -> {
762
// Custom transaction logic
763
return null;
764
});
765
}
766
}
767
```
768
769
### JPA Integration
770
771
```java { .api }
772
// JPA Configuration
773
@Configuration
774
@EnableJpaRepositories(basePackages = "com.example.repository")
775
@EnableTransactionManagement
776
public class JpaConfig {
777
778
@Bean
779
public DataSource dataSource() {
780
HikariDataSource dataSource = new HikariDataSource();
781
dataSource.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
782
dataSource.setUsername("user");
783
dataSource.setPassword("password");
784
return dataSource;
785
}
786
787
@Bean
788
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
789
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
790
em.setDataSource(dataSource());
791
em.setPackagesToScan("com.example.entity");
792
793
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
794
vendorAdapter.setGenerateDdl(true);
795
vendorAdapter.setShowSql(true);
796
em.setJpaVendorAdapter(vendorAdapter);
797
798
Properties properties = new Properties();
799
properties.setProperty("hibernate.hbm2ddl.auto", "update");
800
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
801
em.setJpaProperties(properties);
802
803
return em;
804
}
805
806
@Bean
807
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
808
JpaTransactionManager transactionManager = new JpaTransactionManager();
809
transactionManager.setEntityManagerFactory(emf);
810
return transactionManager;
811
}
812
}
813
814
// JPA Entity
815
@Entity
816
@Table(name = "users")
817
public class User {
818
819
@Id
820
@GeneratedValue(strategy = GenerationType.IDENTITY)
821
private Long id;
822
823
@Column(name = "username", nullable = false, unique = true)
824
private String username;
825
826
@Column(name = "email", nullable = false)
827
private String email;
828
829
@Column(name = "created_date")
830
private LocalDateTime createdDate;
831
832
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
833
private List<Order> orders = new ArrayList<>();
834
835
// Constructors, getters, setters
836
}
837
838
// Repository using EntityManager directly
839
@Repository
840
public class UserJpaRepository {
841
842
@PersistenceContext
843
private EntityManager entityManager;
844
845
public User save(User user) {
846
if (user.getId() == null) {
847
entityManager.persist(user);
848
return user;
849
} else {
850
return entityManager.merge(user);
851
}
852
}
853
854
public User findById(Long id) {
855
return entityManager.find(User.class, id);
856
}
857
858
public List<User> findByUsername(String username) {
859
TypedQuery<User> query = entityManager.createQuery(
860
"SELECT u FROM User u WHERE u.username LIKE :username", User.class);
861
query.setParameter("username", "%" + username + "%");
862
return query.getResultList();
863
}
864
865
public void delete(User user) {
866
if (entityManager.contains(user)) {
867
entityManager.remove(user);
868
} else {
869
entityManager.remove(entityManager.merge(user));
870
}
871
}
872
873
// Native query example
874
@SuppressWarnings("unchecked")
875
public List<User> findUsersWithOrdersInDateRange(LocalDate startDate, LocalDate endDate) {
876
Query query = entityManager.createNativeQuery(
877
"SELECT DISTINCT u.* FROM users u " +
878
"JOIN orders o ON u.id = o.user_id " +
879
"WHERE o.order_date BETWEEN ?1 AND ?2", User.class);
880
881
query.setParameter(1, startDate);
882
query.setParameter(2, endDate);
883
884
return query.getResultList();
885
}
886
}
887
```
888
889
### R2DBC Reactive Data Access
890
891
```java { .api }
892
// R2DBC Configuration
893
@Configuration
894
@EnableR2dbcRepositories
895
public class R2dbcConfig extends AbstractR2dbcConfiguration {
896
897
@Override
898
@Bean
899
public ConnectionFactory connectionFactory() {
900
return ConnectionFactories.get(ConnectionFactoryOptions.builder()
901
.option(DRIVER, "postgresql")
902
.option(HOST, "localhost")
903
.option(PORT, 5432)
904
.option(USER, "user")
905
.option(PASSWORD, "password")
906
.option(DATABASE, "mydb")
907
.build());
908
}
909
910
@Bean
911
public R2dbcEntityTemplate r2dbcEntityTemplate(DatabaseClient databaseClient) {
912
return new R2dbcEntityTemplate(databaseClient);
913
}
914
}
915
916
// Reactive Repository
917
@Repository
918
public class ReactiveUserRepository {
919
920
private final DatabaseClient databaseClient;
921
922
public ReactiveUserRepository(DatabaseClient databaseClient) {
923
this.databaseClient = databaseClient;
924
}
925
926
public Mono<User> findById(Long id) {
927
return databaseClient
928
.sql("SELECT id, username, email, created_date FROM users WHERE id = :id")
929
.bind("id", id)
930
.map((row, metadata) -> {
931
User user = new User();
932
user.setId(row.get("id", Long.class));
933
user.setUsername(row.get("username", String.class));
934
user.setEmail(row.get("email", String.class));
935
user.setCreatedDate(row.get("created_date", LocalDateTime.class));
936
return user;
937
})
938
.one();
939
}
940
941
public Flux<User> findAll() {
942
return databaseClient
943
.sql("SELECT id, username, email, created_date FROM users ORDER BY created_date DESC")
944
.map(this::mapUser)
945
.all();
946
}
947
948
public Mono<User> save(User user) {
949
if (user.getId() == null) {
950
return databaseClient
951
.sql("INSERT INTO users (username, email, created_date) VALUES (:username, :email, :createdDate)")
952
.bind("username", user.getUsername())
953
.bind("email", user.getEmail())
954
.bind("createdDate", user.getCreatedDate())
955
.fetch()
956
.rowsUpdated()
957
.map(count -> user);
958
} else {
959
return databaseClient
960
.sql("UPDATE users SET username = :username, email = :email WHERE id = :id")
961
.bind("id", user.getId())
962
.bind("username", user.getUsername())
963
.bind("email", user.getEmail())
964
.fetch()
965
.rowsUpdated()
966
.map(count -> user);
967
}
968
}
969
970
public Mono<Void> deleteById(Long id) {
971
return databaseClient
972
.sql("DELETE FROM users WHERE id = :id")
973
.bind("id", id)
974
.then();
975
}
976
977
private User mapUser(Row row, RowMetadata metadata) {
978
User user = new User();
979
user.setId(row.get("id", Long.class));
980
user.setUsername(row.get("username", String.class));
981
user.setEmail(row.get("email", String.class));
982
user.setCreatedDate(row.get("created_date", LocalDateTime.class));
983
return user;
984
}
985
}
986
987
// Reactive Service with Transactions
988
@Service
989
public class ReactiveUserService {
990
991
private final ReactiveUserRepository userRepository;
992
private final R2dbcTransactionManager transactionManager;
993
994
public ReactiveUserService(ReactiveUserRepository userRepository,
995
R2dbcTransactionManager transactionManager) {
996
this.userRepository = userRepository;
997
this.transactionManager = transactionManager;
998
}
999
1000
@Transactional
1001
public Mono<User> createUser(User user) {
1002
return userRepository.save(user)
1003
.doOnNext(savedUser -> log.info("User created: {}", savedUser.getId()));
1004
}
1005
1006
public Mono<User> updateUser(Long id, User updatedUser) {
1007
return userRepository.findById(id)
1008
.switchIfEmpty(Mono.error(new UserNotFoundException("User not found: " + id)))
1009
.map(existingUser -> {
1010
existingUser.setUsername(updatedUser.getUsername());
1011
existingUser.setEmail(updatedUser.getEmail());
1012
return existingUser;
1013
})
1014
.flatMap(userRepository::save);
1015
}
1016
1017
// Manual transaction control
1018
public Mono<Void> performTransactionalOperation(User user1, User user2) {
1019
TransactionalOperator operator = TransactionalOperator.create(transactionManager);
1020
1021
return userRepository.save(user1)
1022
.then(userRepository.save(user2))
1023
.then()
1024
.as(operator::transactional);
1025
}
1026
}
1027
```
1028
1029
## Exception Handling
1030
1031
### DAO Exception Hierarchy
1032
1033
```java { .api }
1034
// Common exception handling patterns
1035
@Service
1036
public class UserService {
1037
1038
private final UserRepository userRepository;
1039
1040
public UserService(UserRepository userRepository) {
1041
this.userRepository = userRepository;
1042
}
1043
1044
public User getUserById(Long id) {
1045
try {
1046
return userRepository.findById(id);
1047
} catch (EmptyResultDataAccessException e) {
1048
throw new UserNotFoundException("User not found with id: " + id);
1049
} catch (DataIntegrityViolationException e) {
1050
throw new UserServiceException("Data integrity violation", e);
1051
} catch (DataAccessException e) {
1052
throw new UserServiceException("Database access error", e);
1053
}
1054
}
1055
1056
public User createUser(User user) {
1057
try {
1058
return userRepository.save(user);
1059
} catch (DuplicateKeyException e) {
1060
throw new UserAlreadyExistsException("User already exists: " + user.getUsername());
1061
} catch (DataAccessException e) {
1062
throw new UserServiceException("Failed to create user", e);
1063
}
1064
}
1065
}
1066
```
1067
1068
Spring's data access support provides a comprehensive foundation for working with databases, offering both traditional JDBC operations and modern reactive programming models, all integrated with robust transaction management capabilities.