SpringBoot数据访问
大约 7 分钟
SpringBoot数据访问
SpringBoot数据访问概述
Spring Boot为数据访问提供了全面的支持,通过各种起步依赖简化了数据访问层的配置和开发。它支持关系型数据库、NoSQL数据库以及各种数据访问技术。
数据访问技术栈
JDBC数据访问
添加JDBC起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>配置数据源
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/testdb
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000使用JdbcTemplate
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
// 查询单个对象
public User findById(Long id) {
String sql = "SELECT id, name, email FROM user WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new Object[]{id}, new UserRowMapper());
}
// 查询列表
public List<User> findAll() {
String sql = "SELECT id, name, email FROM user";
return jdbcTemplate.query(sql, new UserRowMapper());
}
// 插入数据
public int insert(User user) {
String sql = "INSERT INTO user (name, email) VALUES (?, ?)";
return jdbcTemplate.update(sql, user.getName(), user.getEmail());
}
// 更新数据
public int update(User user) {
String sql = "UPDATE user SET name = ?, email = ? WHERE id = ?";
return jdbcTemplate.update(sql, user.getName(), user.getEmail(), user.getId());
}
// 删除数据
public int delete(Long id) {
String sql = "DELETE FROM user WHERE id = ?";
return jdbcTemplate.update(sql, id);
}
// 批量操作
public int[] batchInsert(List<User> users) {
String sql = "INSERT INTO user (name, email) VALUES (?, ?)";
return jdbcTemplate.batchUpdate(sql, users, users.size(),
(PreparedStatement ps, User user) -> {
ps.setString(1, user.getName());
ps.setString(2, user.getEmail());
});
}
}
// RowMapper实现
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"));
return user;
}
}JPA数据访问
添加JPA起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>实体类定义
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String name;
@Column(unique = true, nullable = false)
private String email;
@Column(name = "created_at")
@CreationTimestamp
private LocalDateTime createdAt;
@Column(name = "updated_at")
@UpdateTimestamp
private LocalDateTime updatedAt;
// 构造函数、getters和setters
public User() {}
public User(String name, String email) {
this.name = name;
this.email = email;
}
// getters and setters
}Repository接口
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 方法名查询
List<User> findByName(String name);
User findByEmail(String email);
List<User> findByNameContainingIgnoreCase(String name);
// 使用@Query注解
@Query("SELECT u FROM User u WHERE u.name = ?1 AND u.email = ?2")
List<User> findByNameAndEmail(String name, String email);
@Query("SELECT u FROM User u WHERE u.createdAt > :date")
List<User> findCreatedAfter(@Param("date") LocalDateTime date);
// 更新查询
@Modifying
@Transactional
@Query("UPDATE User u SET u.name = :name WHERE u.id = :id")
int updateUserName(@Param("id") Long id, @Param("name") String name);
// 分页查询
@Query("SELECT u FROM User u WHERE u.name LIKE %:keyword%")
Page<User> findByNameKeyword(@Param("keyword") String keyword, Pageable pageable);
}Service层实现
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
public List<User> findAll() {
return userRepository.findAll();
}
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
public User findByEmail(String email) {
return userRepository.findByEmail(email);
}
public User save(User user) {
return userRepository.save(user);
}
public User update(User user) {
if (userRepository.existsById(user.getId())) {
return userRepository.save(user);
}
return null;
}
public void delete(Long id) {
userRepository.deleteById(id);
}
public Page<User> findByName(String name, Pageable pageable) {
return userRepository.findByNameKeyword(name, pageable);
}
}MyBatis数据访问
添加MyBatis起步依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>配置MyBatis
# application.yml
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
configuration:
map-underscore-to-camel-case: true
cache-enabled: trueMapper接口
@Mapper
public interface UserMapper {
@Select("SELECT id, name, email FROM user WHERE id = #{id}")
User findById(Long id);
@Select("SELECT id, name, email FROM user")
List<User> findAll();
@Insert("INSERT INTO user(name, email) VALUES(#{name}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
@Update("UPDATE user SET name = #{name}, email = #{email} WHERE id = #{id}")
int update(User user);
@Delete("DELETE FROM user WHERE id = #{id}")
int delete(Long id);
// 复杂查询
@Select("SELECT u.id, u.name, u.email, p.title as profileTitle " +
"FROM user u LEFT JOIN user_profile p ON u.id = p.user_id " +
"WHERE u.name LIKE CONCAT('%', #{keyword}, '%')")
List<UserProfileDto> searchUsersWithProfile(@Param("keyword") String keyword);
}XML映射文件
<!-- src/main/resources/mapper/UserMapper.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="UserResultMap" type="com.example.entity.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="email" column="email"/>
</resultMap>
<select id="findUsersWithOrders" resultMap="UserResultMap">
SELECT u.id, u.name, u.email
FROM user u
WHERE u.created_at > #{startDate}
ORDER BY u.created_at DESC
</select>
<select id="findUserStatistics" resultType="map">
SELECT
COUNT(*) as totalUsers,
COUNT(CASE WHEN created_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH) THEN 1 END) as newUsers
FROM user
</select>
</mapper>Spring Data JPA高级特性
审计功能
@Entity
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@CreatedBy
private String createdBy;
@LastModifiedBy
private String updatedBy;
// getters and setters
}
@Configuration
@EnableJpaAuditing
public class JpaConfig {
// 配置
}自定义查询
@Repository
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
// 使用Specification进行动态查询
default List<User> findUsersByCriteria(String name, String email) {
return findAll((root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (name != null && !name.isEmpty()) {
predicates.add(criteriaBuilder.like(root.get("name"), "%" + name + "%"));
}
if (email != null && !email.isEmpty()) {
predicates.add(criteriaBuilder.like(root.get("email"), "%" + email + "%"));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
});
}
}
// 自定义Repository实现
public interface UserRepositoryCustom {
List<UserStatisticsDto> getUserStatistics();
}
@Repository
public class UserRepositoryImpl implements UserRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<UserStatisticsDto> getUserStatistics() {
String jpql = "SELECT new com.example.dto.UserStatisticsDto(" +
"COUNT(u), " +
"COUNT(CASE WHEN u.createdAt >= :lastMonth THEN 1 END)) " +
"FROM User u";
TypedQuery<UserStatisticsDto> query = entityManager.createQuery(jpql, UserStatisticsDto.class);
query.setParameter("lastMonth", LocalDateTime.now().minusMonths(1));
return query.getResultList();
}
}事务管理
声明式事务
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserProfileRepository userProfileRepository;
@Transactional
public User createUserWithProfile(User user, UserProfile profile) {
User savedUser = userRepository.save(user);
profile.setUserId(savedUser.getId());
userProfileRepository.save(profile);
return savedUser;
}
@Transactional(readOnly = true)
public List<User> findAllUsers() {
return userRepository.findAll();
}
@Transactional(rollbackFor = Exception.class)
public void batchUpdateUsers(List<User> users) throws Exception {
for (User user : users) {
userRepository.save(user);
// 如果发生异常,整个事务会回滚
}
}
}编程式事务
@Service
public class UserService {
@Autowired
private PlatformTransactionManager transactionManager;
public void saveUserProgrammatically(User user) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(status -> {
try {
userRepository.save(user);
// 其他业务逻辑
return null;
} catch (Exception e) {
status.setRollbackOnly();
throw new RuntimeException(e);
}
});
}
}数据库迁移
Flyway集成
添加依赖:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>配置:
# application.yml
spring:
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true迁移脚本:
-- src/main/resources/db/migration/V1__Create_user_table.sql
CREATE TABLE user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- src/main/resources/db/migration/V2__Create_user_profile_table.sql
CREATE TABLE user_profile (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
title VARCHAR(100),
bio TEXT,
FOREIGN KEY (user_id) REFERENCES user(id)
);连接池配置
HikariCP配置
# application.yml
spring:
datasource:
hikari:
pool-name: HikariPool-1
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
leak-detection-threshold: 60000连接池监控
@Component
public class DataSourceMonitor {
@Autowired
private HikariDataSource dataSource;
@Scheduled(fixedRate = 30000) // 每30秒执行一次
public void monitorConnectionPool() {
HikariPoolMXBean poolBean = dataSource.getHikariPoolMXBean();
log.info("连接池状态 - 活跃连接数: {}, 空闲连接数: {}, 等待连接数: {}",
poolBean.getActiveConnections(),
poolBean.getIdleConnections(),
poolBean.getThreadsAwaitingConnection());
}
}多数据源配置
配置多个数据源
@Configuration
public class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
@Configuration
@EnableJpaRepositories(
basePackages = "com.example.repository.primary",
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager"
)
public class PrimaryDataSourceConfig {
@Autowired
@Qualifier("primaryDataSource")
private DataSource primaryDataSource;
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(primaryDataSource);
em.setPackagesToScan("com.example.entity.primary");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
@Bean
@Primary
public PlatformTransactionManager primaryTransactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(primaryEntityManagerFactory().getObject());
return transactionManager;
}
}配置文件:
# application.yml
spring:
datasource:
primary:
url: jdbc:mysql://localhost:3306/primary_db
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
url: jdbc:mysql://localhost:3306/secondary_db
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver缓存集成
Spring Cache集成
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Cacheable(value = "users", key = "#id")
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
@CacheEvict(value = "users", key = "#user.id")
public User save(User user) {
return userRepository.save(user);
}
@CacheEvict(value = "users", key = "#id")
public void delete(Long id) {
userRepository.deleteById(id);
}
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsersCache() {
// 清除所有用户缓存
}
}Redis缓存配置
# application.yml
spring:
cache:
type: redis
redis:
host: localhost
port: 6379
timeout: 2000ms@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}测试数据访问层
Repository测试
@DataJpaTest
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
void shouldFindUserByEmail() {
// 准备测试数据
User user = new User("张三", "zhangsan@example.com");
entityManager.persistAndFlush(user);
// 执行测试
User found = userRepository.findByEmail("zhangsan@example.com");
// 验证结果
assertThat(found).isNotNull();
assertThat(found.getName()).isEqualTo("张三");
}
@Test
void shouldFindUsersByNameContaining() {
// 准备测试数据
entityManager.persist(new User("张三", "zhangsan@example.com"));
entityManager.persist(new User("张四", "zhangsi@example.com"));
entityManager.persist(new User("李四", "lisi@example.com"));
entityManager.flush();
// 执行测试
List<User> users = userRepository.findByNameContainingIgnoreCase("张");
// 验证结果
assertThat(users).hasSize(2);
assertThat(users).extracting(User::getName)
.containsExactlyInAnyOrder("张三", "张四");
}
}集成测试
@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
@Transactional
@Rollback
void shouldCreateUserWithProfile() {
// 准备测试数据
User user = new User("测试用户", "test@example.com");
UserProfile profile = new UserProfile();
profile.setTitle("测试职位");
// 执行测试
User savedUser = userService.createUserWithProfile(user, profile);
// 验证结果
assertThat(savedUser.getId()).isNotNull();
assertThat(savedUser.getName()).isEqualTo("测试用户");
// 验证数据库状态
User foundUser = userRepository.findById(savedUser.getId()).orElse(null);
assertThat(foundUser).isNotNull();
assertThat(foundUser.getEmail()).isEqualTo("test@example.com");
}
}通过以上内容,我们可以全面了解Spring Boot数据访问的各个方面,包括JDBC、JPA、MyBatis、事务管理、数据库迁移、多数据源配置、缓存集成和测试等。Spring Boot通过起步依赖和自动配置,大大简化了数据访问层的开发工作。
