跳到主要内容

数据访问

Spring 提供了强大的数据访问支持,简化了数据库操作。本章介绍 Spring 的数据访问抽象、JDBC 支持以及 ORM 集成。

Spring 数据访问的设计理念

Spring 数据访问模块的设计遵循以下原则:

统一的异常体系:将各种数据访问技术的异常统一转换为 Spring 的 DataAccessException 层次结构,避免代码依赖特定技术的异常类。

统一的 API:提供模板类(如 JdbcTemplate)封装通用操作,开发者只需关注业务逻辑。

与 IOC 容器集成:数据源、事务管理器等都可以作为 Bean 进行配置和管理。

不侵入业务代码:业务代码不需要实现 Spring 特定的接口。

数据源配置

数据源是数据库连接的工厂,Spring 支持多种数据源配置方式。

使用 HikariCP(推荐)

HikariCP 是目前性能最高的连接池实现:

<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
@Configuration
public class DataSourceConfig {

@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public DataSource dataSource() {
return new HikariDataSource();
}
}

application.properties:

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=HikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000

使用 Druid

Druid 是阿里巴巴开源的数据库连接池,提供强大的监控功能:

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.20</version>
</dependency>
@Configuration
public class DruidConfig {

@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid")
public DataSource druidDataSource() {
return new DruidDataSource();
}

@Bean
public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {
ServletRegistrationBean<StatViewServlet> registrationBean =
new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
registrationBean.addInitParameter("loginUsername", "admin");
registrationBean.addInitParameter("loginPassword", "admin");
return registrationBean;
}
}

嵌入式数据库

开发和测试阶段可以使用嵌入式数据库:

@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:data.sql")
.build();
}

JdbcTemplate

JdbcTemplate 是 Spring JDBC 的核心类,简化了 JDBC 操作。

基本配置

@Configuration
public class JdbcConfig {

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}

查询操作

查询单个值

@Repository
public class UserDao {

@Autowired
private JdbcTemplate jdbcTemplate;

public String findNameById(Long id) {
String sql = "SELECT name FROM users WHERE id = ?";
return jdbcTemplate.queryForObject(sql, String.class, id);
}

public int count() {
return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Integer.class);
}
}

查询实体对象

public User findById(Long id) {
String sql = "SELECT id, name, email, created_at FROM users WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new UserRowMapper(), id);
}

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.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
user.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
return user;
}
}

使用 BeanPropertyRowMapper

如果数据库列名与 Java Bean 属性名匹配(支持驼峰转换),可以使用 BeanPropertyRowMapper:

public List<User> findAll() {
String sql = "SELECT id, name, email, created_at FROM users";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
}

查询列表

public List<User> findByNameLike(String name) {
String sql = "SELECT id, name, email FROM users WHERE name LIKE ?";
return jdbcTemplate.query(sql, new UserRowMapper(), "%" + name + "%");
}

使用 ResultSetExtractor

需要处理整个结果集时使用:

public Map<Long, User> findAllAsMap() {
String sql = "SELECT id, name, email FROM users";
return jdbcTemplate.query(sql, rs -> {
Map<Long, User> map = new HashMap<>();
while (rs.next()) {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
map.put(user.getId(), user);
}
return map;
});
}

更新操作

插入、更新、删除

public int insert(User user) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
return jdbcTemplate.update(sql, user.getName(), user.getEmail());
}

public int update(User user) {
String sql = "UPDATE users SET name = ?, email = ? WHERE id = ?";
return jdbcTemplate.update(sql, user.getName(), user.getEmail(), user.getId());
}

public int delete(Long id) {
String sql = "DELETE FROM users WHERE id = ?";
return jdbcTemplate.update(sql, id);
}

获取自增主键

public Long insertAndGetId(User user) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
KeyHolder keyHolder = new GeneratedKeyHolder();

jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, user.getName());
ps.setString(2, user.getEmail());
return ps;
}, keyHolder);

return keyHolder.getKey().longValue();
}

批量操作

public void batchInsert(List<User> users) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";

jdbcTemplate.batchUpdate(sql, users, users.size(), (ps, user) -> {
ps.setString(1, user.getName());
ps.setString(2, user.getEmail());
});
}

调用存储过程

public User callProcedure(Long id) {
SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("get_user_by_id")
.returningResultSet("user", new UserRowMapper());

Map<String, Object> inParams = new HashMap<>();
inParams.put("p_user_id", id);

Map<String, Object> result = jdbcCall.execute(inParams);

@SuppressWarnings("unchecked")
List<User> users = (List<User>) result.get("user");
return users.isEmpty() ? null : users.get(0);
}

NamedParameterJdbcTemplate

NamedParameterJdbcTemplate 支持命名参数,代码更清晰:

@Repository
public class UserDao {

@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public User findById(Long id) {
String sql = "SELECT id, name, email FROM users WHERE id = :id";
MapSqlParameterSource params = new MapSqlParameterSource("id", id);
return namedParameterJdbcTemplate.queryForObject(sql, params, new UserRowMapper());
}

public List<User> findByIds(List<Long> ids) {
String sql = "SELECT id, name, email FROM users WHERE id IN (:ids)";
MapSqlParameterSource params = new MapSqlParameterSource("ids", ids);
return namedParameterJdbcTemplate.query(sql, params, new UserRowMapper());
}

public int update(User user) {
String sql = "UPDATE users SET name = :name, email = :email WHERE id = :id";
SqlParameterSource params = new BeanPropertySqlParameterSource(user);
return namedParameterJdbcTemplate.update(sql, params);
}
}

ORM 集成

Spring 提供了对主流 ORM 框架的集成支持。

JPA 集成

配置

@Configuration
@EnableJpaRepositories(basePackages = "com.example.repository")
public class JpaConfig {

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.example.entity");

HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);

Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "update");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
properties.setProperty("hibernate.show_sql", "true");
em.setJpaProperties(properties);

return em;
}

@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}

实体类

@Entity
@Table(name = "users")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, length = 100)
private String name;

@Column(unique = true)
private String email;

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Order> orders = new ArrayList<>();

@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime createdAt;
}

Repository 接口

public interface UserRepository extends JpaRepository<User, Long> {

Optional<User> findByEmail(String email);

List<User> findByNameContaining(String name);

@Query("SELECT u FROM User u WHERE u.createdAt > :date")
List<User> findCreatedAfter(@Param("date") LocalDateTime date);

@Modifying
@Query("UPDATE User u SET u.email = :email WHERE u.id = :id")
int updateEmail(@Param("id") Long id, @Param("email") String email);
}

使用

@Service
@Transactional
public class UserService {

@Autowired
private UserRepository userRepository;

public User create(User user) {
return userRepository.save(user);
}

public Optional<User> findById(Long id) {
return userRepository.findById(id);
}

public List<User> findAll() {
return userRepository.findAll();
}

public User update(User user) {
return userRepository.save(user);
}

public void delete(Long id) {
userRepository.deleteById(id);
}

public Optional<User> findByEmail(String email) {
return userRepository.findByEmail(email);
}
}

MyBatis 集成

Spring 与 MyBatis 的集成非常常用,详见 MyBatis 教程。

事务管理

数据访问与事务管理紧密相关,详见事务管理章节。基本用法:

@Service
public class UserService {

@Autowired
private UserDao userDao;

@Autowired
private AccountDao accountDao;

@Transactional
public void registerUser(User user) {
userDao.insert(user);
Account account = new Account();
account.setUserId(user.getId());
accountDao.insert(account);
}
}

小结

本章介绍了 Spring 的数据访问支持:

  1. 数据源配置:HikariCP、Druid、嵌入式数据库
  2. JdbcTemplate:查询、更新、批量操作
  3. NamedParameterJdbcTemplate:命名参数支持
  4. JPA 集成:实体类、Repository 接口
  5. 事务管理:与数据访问的配合

Spring 的数据访问模块提供了统一的编程模型,无论是使用 JDBC 还是 ORM,都能获得一致的体验。