Comprehensive application framework and inversion of control container for the Java platform providing dependency injection, AOP, data access, transaction management, and web framework capabilities
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.
<!-- Spring JDBC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.39</version>
</dependency>
<!-- Spring Transaction Management -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.39</version>
</dependency>
<!-- Spring ORM -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.39</version>
</dependency>
<!-- Spring R2DBC (Reactive) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-r2dbc</artifactId>
<version>5.3.39</version>
</dependency>
<!-- Database driver (example: H2) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.214</version>
</dependency>// JDBC Core
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementSetter;
// Data Source
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
// Transaction Management
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.EnableTransactionManagement;
// ORM Integration
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
// Exception Translation
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.DuplicateKeyException;
// R2DBC (Reactive)
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;// Central class in JDBC core package providing JDBC operations
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
// Query methods
public <T> T queryForObject(String sql, Class<T> requiredType);
public <T> T queryForObject(String sql, Class<T> requiredType, Object... args);
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args);
public <T> List<T> query(String sql, RowMapper<T> rowMapper);
public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args);
public <T> List<T> query(String sql, PreparedStatementSetter pss, RowMapper<T> rowMapper);
public <T> T query(String sql, ResultSetExtractor<T> rse);
// Update methods
public int update(String sql);
public int update(String sql, Object... args);
public int update(String sql, PreparedStatementSetter pss);
public int update(PreparedStatementCreator psc);
// Batch operations
public int[] batchUpdate(String sql, List<Object[]> batchArgs);
public int[] batchUpdate(String sql, BatchPreparedStatementSetter pss);
// Execute methods
public void execute(String sql);
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action);
}
// RowMapper interface for mapping rows of ResultSet
@FunctionalInterface
public interface RowMapper<T> {
T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
// Interface used by JdbcTemplate for extracting values from ResultSet
@FunctionalInterface
public interface ResultSetExtractor<T> {
T extractData(ResultSet rs) throws SQLException, DataAccessException;
}
// Interface for setting values on PreparedStatement
@FunctionalInterface
public interface PreparedStatementSetter {
void setValues(PreparedStatement ps) throws SQLException;
}// Template class with support for named parameters
public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations {
public NamedParameterJdbcTemplate(DataSource dataSource);
public NamedParameterJdbcTemplate(JdbcOperations classicJdbcTemplate);
// Query methods with named parameters
public <T> T queryForObject(String sql, Map<String, ?> paramMap, Class<T> requiredType);
public <T> T queryForObject(String sql, SqlParameterSource paramSource, Class<T> requiredType);
public <T> T queryForObject(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper);
public <T> List<T> query(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper);
public <T> List<T> query(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper);
// Update methods with named parameters
public int update(String sql, Map<String, ?> paramMap);
public int update(String sql, SqlParameterSource paramSource);
// Batch operations
public int[] batchUpdate(String sql, Map<String, ?>[] batchValues);
public int[] batchUpdate(String sql, SqlParameterSource[] batchArgs);
}
// Interface for parameter sources
public interface SqlParameterSource {
boolean hasValue(String paramName);
Object getValue(String paramName) throws IllegalArgumentException;
int getSqlType(String paramName);
String getTypeName(String paramName);
String[] getParameterNames();
}
// Map-based parameter source
public class MapSqlParameterSource implements SqlParameterSource {
public MapSqlParameterSource();
public MapSqlParameterSource(String paramName, Object value);
public MapSqlParameterSource(Map<String, ?> values);
public MapSqlParameterSource addValue(String paramName, Object value);
public MapSqlParameterSource addValue(String paramName, Object value, int sqlType);
public MapSqlParameterSource addValues(Map<String, ?> values);
}// Central interface for transaction management
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
// Interface that defines Spring-compliant transaction properties
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;
int getPropagationBehavior();
int getIsolationLevel();
int getTimeout();
boolean isReadOnly();
String getName();
}
// Representation of status of transaction
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
boolean isCompleted();
}
// Template class that simplifies programmatic transaction demarcation
public class TransactionTemplate extends DefaultTransactionDefinition implements TransactionOperations, InitializingBean {
public TransactionTemplate();
public TransactionTemplate(PlatformTransactionManager transactionManager);
public <T> T execute(TransactionCallback<T> action) throws TransactionException;
public void execute(TransactionCallbackWithoutResult action) throws TransactionException;
}// Describes transaction attributes on individual methods or classes
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
// Enables Spring's annotation-driven transaction management capability
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
// Propagation enum
public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
}
// Isolation enum
public enum Isolation {
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
}// PlatformTransactionManager implementation for single JDBC DataSource
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
public DataSourceTransactionManager();
public DataSourceTransactionManager(DataSource dataSource);
public void setDataSource(DataSource dataSource);
public DataSource getDataSource();
public void setEnforceReadOnly(boolean enforceReadOnly);
}
// PlatformTransactionManager for JPA EntityManagerFactory
public class JpaTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {
public JpaTransactionManager();
public JpaTransactionManager(EntityManagerFactory emf);
public void setEntityManagerFactory(EntityManagerFactory emf);
public EntityManagerFactory getEntityManagerFactory();
public void setDataSource(DataSource dataSource);
}
// PlatformTransactionManager for Hibernate SessionFactory
public class HibernateTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {
public HibernateTransactionManager();
public HibernateTransactionManager(SessionFactory sessionFactory);
public void setSessionFactory(SessionFactory sessionFactory);
public SessionFactory getSessionFactory();
public void setDataSource(DataSource dataSource);
}// FactoryBean for creating JPA EntityManagerFactory
public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryBean {
public void setPersistenceUnitName(String persistenceUnitName);
public void setPersistenceUnitPostProcessors(PersistenceUnitPostProcessor... postProcessors);
@Override
protected EntityManagerFactory createNativeEntityManagerFactory() throws PersistenceException;
}
// Extended EntityManagerFactory interface for container integration
public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManagerFactoryBean
implements ResourceLoaderAware, LoadTimeWeaverAware {
public void setDataSource(DataSource dataSource);
public void setPackagesToScan(String... packagesToScan);
public void setJpaVendorAdapter(JpaVendorAdapter jpaVendorAdapter);
public void setJpaProperties(Properties jpaProperties);
}
// JPA Vendor Adapter for Hibernate
public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter {
public void setDatabase(Database database);
public void setDatabasePlatform(String databasePlatform);
public void setGenerateDdl(boolean generateDdl);
public void setShowSql(boolean showSql);
}// FactoryBean for creating Hibernate SessionFactory
public class LocalSessionFactoryBean extends HibernateExceptionTranslator
implements FactoryBean<SessionFactory>, ResourceLoaderAware, InitializingBean, DisposableBean {
public void setDataSource(DataSource dataSource);
public void setPackagesToScan(String... packagesToScan);
public void setAnnotatedClasses(Class<?>... annotatedClasses);
public void setHibernateProperties(Properties hibernateProperties);
public void setConfigLocation(Resource configLocation);
}
// Helper class for Hibernate data access (legacy approach)
public class HibernateTemplate extends HibernateAccessor implements HibernateOperations {
public HibernateTemplate();
public HibernateTemplate(SessionFactory sessionFactory);
// CRUD operations
public void save(Object entity);
public void update(Object entity);
public void saveOrUpdate(Object entity);
public void delete(Object entity);
public <T> T get(Class<T> entityClass, Serializable id);
public <T> T load(Class<T> entityClass, Serializable id);
// Query operations
public List<?> find(String queryString, Object... values);
public <T> List<T> findByNamedParam(String queryString, String[] paramNames, Object[] values);
}// Configuration base class for R2DBC
public abstract class AbstractR2dbcConfiguration {
@Bean
public abstract ConnectionFactory connectionFactory();
@Bean
public R2dbcTransactionManager transactionManager() {
return new R2dbcTransactionManager(connectionFactory());
}
@Bean
public DatabaseClient databaseClient() {
return DatabaseClient.builder()
.connectionFactory(connectionFactory())
.build();
}
}
// Reactive database client
public interface DatabaseClient {
GenericExecuteSpec sql(String sql);
GenericExecuteSpec sql(Supplier<String> sqlSupplier);
// Execute spec for generic SQL operations
interface GenericExecuteSpec {
GenericExecuteSpec bind(Object identifier, Object value);
GenericExecuteSpec bind(int index, Object value);
GenericExecuteSpec bindNull(Object identifier, Class<?> type);
FetchSpec<Map<String, Object>> fetch();
<T> FetchSpec<T> map(Function<Row, T> mappingFunction);
<T> FetchSpec<T> map(BiFunction<Row, RowMetadata, T> mappingFunction);
Mono<Void> then();
}
// Fetch specification
interface FetchSpec<T> {
Mono<T> one();
Mono<T> first();
Flux<T> all();
Mono<Long> rowsUpdated();
}
}
// R2DBC Transaction Manager
public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
implements ResourceTransactionManager, InitializingBean {
public R2dbcTransactionManager(ConnectionFactory connectionFactory);
public void setConnectionFactory(ConnectionFactory connectionFactory);
public ConnectionFactory getConnectionFactory();
}@Repository
public class UserRepository {
private final JdbcTemplate jdbcTemplate;
public UserRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// Simple query for object
public User findById(Long id) {
String sql = "SELECT id, username, email, created_date FROM users WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new UserRowMapper(), id);
}
// Query for list
public List<User> findByEmail(String email) {
String sql = "SELECT id, username, email, created_date FROM users WHERE email LIKE ?";
return jdbcTemplate.query(sql, new UserRowMapper(), "%" + email + "%");
}
// Insert operation
public void save(User user) {
String sql = "INSERT INTO users (username, email, created_date) VALUES (?, ?, ?)";
jdbcTemplate.update(sql, user.getUsername(), user.getEmail(), user.getCreatedDate());
}
// Update operation
public void update(User user) {
String sql = "UPDATE users SET username = ?, email = ? WHERE id = ?";
int rowsAffected = jdbcTemplate.update(sql, user.getUsername(), user.getEmail(), user.getId());
if (rowsAffected == 0) {
throw new EmptyResultDataAccessException("User not found: " + user.getId(), 1);
}
}
// Delete operation
public void delete(Long id) {
String sql = "DELETE FROM users WHERE id = ?";
jdbcTemplate.update(sql, id);
}
// Custom RowMapper
private static class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
user.setUsername(rs.getString("username"));
user.setEmail(rs.getString("email"));
user.setCreatedDate(rs.getTimestamp("created_date").toLocalDateTime());
return user;
}
}
}@Repository
public class OrderRepository {
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public OrderRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
// Named parameters with Map
public List<Order> findByUserId(Long userId) {
String sql = "SELECT id, user_id, total_amount, order_date FROM orders WHERE user_id = :userId";
Map<String, Object> params = new HashMap<>();
params.put("userId", userId);
return namedParameterJdbcTemplate.query(sql, params, new OrderRowMapper());
}
// Named parameters with MapSqlParameterSource
public void save(Order order) {
String sql = "INSERT INTO orders (user_id, total_amount, order_date) VALUES (:userId, :totalAmount, :orderDate)";
MapSqlParameterSource params = new MapSqlParameterSource()
.addValue("userId", order.getUserId())
.addValue("totalAmount", order.getTotalAmount())
.addValue("orderDate", order.getOrderDate());
namedParameterJdbcTemplate.update(sql, params);
}
// Batch operations
public void saveBatch(List<Order> orders) {
String sql = "INSERT INTO orders (user_id, total_amount, order_date) VALUES (:userId, :totalAmount, :orderDate)";
SqlParameterSource[] batchParams = orders.stream()
.map(order -> new MapSqlParameterSource()
.addValue("userId", order.getUserId())
.addValue("totalAmount", order.getTotalAmount())
.addValue("orderDate", order.getOrderDate()))
.toArray(SqlParameterSource[]::new);
namedParameterJdbcTemplate.batchUpdate(sql, batchParams);
}
// Complex queries with multiple parameters
public List<Order> findOrdersByDateRange(LocalDateTime startDate, LocalDateTime endDate, OrderStatus status) {
String sql = """
SELECT id, user_id, total_amount, order_date, status
FROM orders
WHERE order_date BETWEEN :startDate AND :endDate
AND status = :status
ORDER BY order_date DESC
""";
MapSqlParameterSource params = new MapSqlParameterSource()
.addValue("startDate", startDate)
.addValue("endDate", endDate)
.addValue("status", status.name());
return namedParameterJdbcTemplate.query(sql, params, new OrderRowMapper());
}
}// Configuration
@Configuration
@EnableTransactionManagement
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
dataSource.setUsername("user");
dataSource.setPassword("password");
dataSource.setMaximumPoolSize(20);
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
// Service with declarative transactions
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final EmailService emailService;
public OrderService(OrderRepository orderRepository, PaymentService paymentService, EmailService emailService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
this.emailService = emailService;
}
@Transactional
public Order createOrder(Order order) {
// Save order
Order savedOrder = orderRepository.save(order);
// Process payment
Payment payment = paymentService.processPayment(savedOrder);
// Send confirmation email
emailService.sendOrderConfirmation(savedOrder);
return savedOrder;
}
@Transactional(readOnly = true)
public List<Order> getUserOrders(Long userId) {
return orderRepository.findByUserId(userId);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId);
if (order.getStatus() == OrderStatus.CONFIRMED) {
paymentService.refundPayment(order.getPaymentId());
order.setStatus(OrderStatus.CANCELLED);
orderRepository.update(order);
emailService.sendCancellationNotification(order);
}
}
@Transactional(rollbackFor = {PaymentException.class, EmailException.class})
public void processOrderWithStrictRollback(Order order) {
orderRepository.save(order);
paymentService.processPayment(order); // Throws PaymentException
emailService.sendConfirmation(order); // Throws EmailException
}
// Custom transaction attributes
@Transactional(
isolation = Isolation.REPEATABLE_READ,
timeout = 30,
rollbackFor = Exception.class
)
public void complexOrderProcessing(Order order) {
// Complex business logic with specific transaction requirements
}
}@Service
public class AccountService {
private final AccountRepository accountRepository;
private final TransactionTemplate transactionTemplate;
public AccountService(AccountRepository accountRepository, PlatformTransactionManager transactionManager) {
this.accountRepository = accountRepository;
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
transactionTemplate.execute(status -> {
try {
Account fromAccount = accountRepository.findById(fromAccountId);
Account toAccount = accountRepository.findById(toAccountId);
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException("Not enough funds");
}
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
accountRepository.update(fromAccount);
accountRepository.update(toAccount);
return null; // TransactionCallbackWithoutResult
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
});
}
// With return value
public Account createAccountWithInitialDeposit(String accountNumber, BigDecimal initialDeposit) {
return transactionTemplate.execute(status -> {
Account account = new Account();
account.setAccountNumber(accountNumber);
account.setBalance(initialDeposit);
Long accountId = accountRepository.save(account);
account.setId(accountId);
// Log transaction
auditService.logAccountCreation(account);
return account;
});
}
// Custom transaction definition
public void performOperationWithCustomTransaction() {
DefaultTransactionDefinition txDef = new DefaultTransactionDefinition();
txDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
txDef.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
txDef.setTimeout(30);
TransactionTemplate customTemplate = new TransactionTemplate(transactionManager, txDef);
customTemplate.execute(status -> {
// Custom transaction logic
return null;
});
}
}// JPA Configuration
@Configuration
@EnableJpaRepositories(basePackages = "com.example.repository")
@EnableTransactionManagement
public class JpaConfig {
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
dataSource.setUsername("user");
dataSource.setPassword("password");
return dataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("com.example.entity");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setShowSql(true);
em.setJpaVendorAdapter(vendorAdapter);
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "update");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
em.setJpaProperties(properties);
return em;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
}
// JPA Entity
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username", nullable = false, unique = true)
private String username;
@Column(name = "email", nullable = false)
private String email;
@Column(name = "created_date")
private LocalDateTime createdDate;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Order> orders = new ArrayList<>();
// Constructors, getters, setters
}
// Repository using EntityManager directly
@Repository
public class UserJpaRepository {
@PersistenceContext
private EntityManager entityManager;
public User save(User user) {
if (user.getId() == null) {
entityManager.persist(user);
return user;
} else {
return entityManager.merge(user);
}
}
public User findById(Long id) {
return entityManager.find(User.class, id);
}
public List<User> findByUsername(String username) {
TypedQuery<User> query = entityManager.createQuery(
"SELECT u FROM User u WHERE u.username LIKE :username", User.class);
query.setParameter("username", "%" + username + "%");
return query.getResultList();
}
public void delete(User user) {
if (entityManager.contains(user)) {
entityManager.remove(user);
} else {
entityManager.remove(entityManager.merge(user));
}
}
// Native query example
@SuppressWarnings("unchecked")
public List<User> findUsersWithOrdersInDateRange(LocalDate startDate, LocalDate endDate) {
Query query = entityManager.createNativeQuery(
"SELECT DISTINCT u.* FROM users u " +
"JOIN orders o ON u.id = o.user_id " +
"WHERE o.order_date BETWEEN ?1 AND ?2", User.class);
query.setParameter(1, startDate);
query.setParameter(2, endDate);
return query.getResultList();
}
}// R2DBC Configuration
@Configuration
@EnableR2dbcRepositories
public class R2dbcConfig extends AbstractR2dbcConfiguration {
@Override
@Bean
public ConnectionFactory connectionFactory() {
return ConnectionFactories.get(ConnectionFactoryOptions.builder()
.option(DRIVER, "postgresql")
.option(HOST, "localhost")
.option(PORT, 5432)
.option(USER, "user")
.option(PASSWORD, "password")
.option(DATABASE, "mydb")
.build());
}
@Bean
public R2dbcEntityTemplate r2dbcEntityTemplate(DatabaseClient databaseClient) {
return new R2dbcEntityTemplate(databaseClient);
}
}
// Reactive Repository
@Repository
public class ReactiveUserRepository {
private final DatabaseClient databaseClient;
public ReactiveUserRepository(DatabaseClient databaseClient) {
this.databaseClient = databaseClient;
}
public Mono<User> findById(Long id) {
return databaseClient
.sql("SELECT id, username, email, created_date FROM users WHERE id = :id")
.bind("id", id)
.map((row, metadata) -> {
User user = new User();
user.setId(row.get("id", Long.class));
user.setUsername(row.get("username", String.class));
user.setEmail(row.get("email", String.class));
user.setCreatedDate(row.get("created_date", LocalDateTime.class));
return user;
})
.one();
}
public Flux<User> findAll() {
return databaseClient
.sql("SELECT id, username, email, created_date FROM users ORDER BY created_date DESC")
.map(this::mapUser)
.all();
}
public Mono<User> save(User user) {
if (user.getId() == null) {
return databaseClient
.sql("INSERT INTO users (username, email, created_date) VALUES (:username, :email, :createdDate)")
.bind("username", user.getUsername())
.bind("email", user.getEmail())
.bind("createdDate", user.getCreatedDate())
.fetch()
.rowsUpdated()
.map(count -> user);
} else {
return databaseClient
.sql("UPDATE users SET username = :username, email = :email WHERE id = :id")
.bind("id", user.getId())
.bind("username", user.getUsername())
.bind("email", user.getEmail())
.fetch()
.rowsUpdated()
.map(count -> user);
}
}
public Mono<Void> deleteById(Long id) {
return databaseClient
.sql("DELETE FROM users WHERE id = :id")
.bind("id", id)
.then();
}
private User mapUser(Row row, RowMetadata metadata) {
User user = new User();
user.setId(row.get("id", Long.class));
user.setUsername(row.get("username", String.class));
user.setEmail(row.get("email", String.class));
user.setCreatedDate(row.get("created_date", LocalDateTime.class));
return user;
}
}
// Reactive Service with Transactions
@Service
public class ReactiveUserService {
private final ReactiveUserRepository userRepository;
private final R2dbcTransactionManager transactionManager;
public ReactiveUserService(ReactiveUserRepository userRepository,
R2dbcTransactionManager transactionManager) {
this.userRepository = userRepository;
this.transactionManager = transactionManager;
}
@Transactional
public Mono<User> createUser(User user) {
return userRepository.save(user)
.doOnNext(savedUser -> log.info("User created: {}", savedUser.getId()));
}
public Mono<User> updateUser(Long id, User updatedUser) {
return userRepository.findById(id)
.switchIfEmpty(Mono.error(new UserNotFoundException("User not found: " + id)))
.map(existingUser -> {
existingUser.setUsername(updatedUser.getUsername());
existingUser.setEmail(updatedUser.getEmail());
return existingUser;
})
.flatMap(userRepository::save);
}
// Manual transaction control
public Mono<Void> performTransactionalOperation(User user1, User user2) {
TransactionalOperator operator = TransactionalOperator.create(transactionManager);
return userRepository.save(user1)
.then(userRepository.save(user2))
.then()
.as(operator::transactional);
}
}// Common exception handling patterns
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
try {
return userRepository.findById(id);
} catch (EmptyResultDataAccessException e) {
throw new UserNotFoundException("User not found with id: " + id);
} catch (DataIntegrityViolationException e) {
throw new UserServiceException("Data integrity violation", e);
} catch (DataAccessException e) {
throw new UserServiceException("Database access error", e);
}
}
public User createUser(User user) {
try {
return userRepository.save(user);
} catch (DuplicateKeyException e) {
throw new UserAlreadyExistsException("User already exists: " + user.getUsername());
} catch (DataAccessException e) {
throw new UserServiceException("Failed to create user", e);
}
}
}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.
Install with Tessl CLI
npx tessl i tessl/maven-org-springframework--spring