Spring Data JPA 是Spring Data项目的一部分,旨在极大地简化JPA(Java Persistence API)的使用。它通过提供基于Repository接口的编程模型,让我们无需编写任何实现代码就能完成大多数数据访问操作。
核心概念:
Entity
:一个使用@Entity
注解的普通Java对象(POJO),它映射到数据库中的一张表。Repository
:一个接口,我们通过继承Spring Data JPA提供的JpaRepository
或CrudRepository
来获得丰富的CRUD(创建、读取、更新、删除)和分页查询方法。findByEmail(String email)
会自动生成SELECT * FROM user WHERE email = ?
。1. 添加依赖
在pom.xml
中添加spring-boot-starter-data-jpa
和数据库驱动(如H2)。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>com.h2databasegroupId>
<artifactId>h2artifactId>
<scope>runtimescope>
dependency>
2. 配置数据源
在application.properties
中配置数据库连接信息。
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
3. 创建Entity
import javax.persistence.*;
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
// Getters and Setters
}
4. 创建Repository接口
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
5. 使用Repository
@Service
public class CustomerService {
@Autowired
private CustomerRepository repository;
public void demonstrateJpa() {
// 保存客户
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
// 按姓氏查询
List<Customer> bauers = repository.findByLastName("Bauer");
bauers.forEach(System.out::println);
}
}
MyBatis 是一个优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。它可以使用简单的XML或注解来配置和映射原始类型、接口和Java POJO(Plain Old Java Objects)为数据库中的记录。
与JPA不同,MyBatis将开发者从ORM的复杂性中解放出来,给予了完全控制SQL的能力,因此在处理复杂查询和数据库优化时更具灵活性。
1. 添加依赖
使用MyBatis官方提供的Spring Boot Starter mybatis-spring-boot-starter
。
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.2version>
dependency>
2. 配置MyBatis
在application.properties
中指定Mapper XML文件的位置。
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.configuration.map-underscore-to-camel-case=true
3. 创建Model和Mapper接口
// Model
public class City {
private Long id;
private String name;
private String state;
// Getters and Setters
}
// Mapper接口
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface CityMapper {
@Select("SELECT * FROM city WHERE state = #{state}")
City findByState(String state);
}
4. 创建Mapper XML文件 (可选,用于复杂SQL)
resources/mapper/CityMapper.xml
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.CityMapper">
<select id="findByName" resultType="com.example.demo.model.City">
SELECT * FROM city WHERE name = #{name}
select>
mapper>
5. 使用Mapper
@RestController
public class CityController {
@Autowired
private CityMapper cityMapper;
@GetMapping("/city/{state}")
public City getCityByState(@PathVariable String state) {
return cityMapper.findByState(state);
}
}
在某些业务场景下,一个应用可能需要连接多个不同的数据库,例如读写分离、或连接不同业务的数据库。Spring Boot本身不直接支持多数据源的自动配置,但我们可以通过手动配置来实现。
基本思路:
DataSource
Bean。SqlSessionFactory
(MyBatis)或EntityManagerFactory
(JPA)。TransactionManager
(事务管理器)。@Primary
注解指定一个主数据源,当不明确指定时,默认使用主数据源。1. 配置文件
# Primary DataSource
spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/db1
spring.datasource.primary.username=root
spring.datasource.primary.password=password
# Secondary DataSource
spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/db2
spring.datasource.secondary.username=root
spring.datasource.secondary.password=password
2. 数据源配置类
@Configuration
public class DataSourceConfig {
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
3. 为每个数据源配置MyBatis
这部分配置较为复杂,需要为每个数据源创建SqlSessionFactory
和SqlSessionTemplate
,并使用@MapperScan
指定每个Mapper接口属于哪个数据源。通常会为每个数据源创建一个单独的配置类。
数据库连接是一种昂贵的资源。数据库连接池通过维护一组可重用的数据库连接,避免了为每个请求都创建和销毁连接的开销,从而显著提升应用性能。
Spring Boot 2.x 默认使用 HikariCP 作为数据库连接池,它以其高性能和稳定性而闻名。我们几乎不需要任何配置就可以获得一个表现优异的连接池。当然,也可以通过application.properties
对其进行微调。
常用配置项:
spring.datasource.hikari.minimum-idle
:最小空闲连接数。spring.datasource.hikari.maximum-pool-size
:最大连接数。spring.datasource.hikari.idle-timeout
:空闲连接的超时时间(毫秒),超时后连接会被释放。spring.datasource.hikari.connection-timeout
:获取连接的超时时间(毫秒)。spring.datasource.hikari.max-lifetime
:连接的最大生命周期(毫秒),到期后连接会被关闭。在application.properties
中进行配置:
# HikariCP Configuration
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.pool-name=MyWebAppPool
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
事务是一组必须作为一个整体成功或失败的数据库操作。Spring通过@Transactional
注解提供了强大的声明式事务管理功能,使得我们可以将业务代码与事务管理代码完全解耦。
核心概念:
@Transactional
:可以应用在类或方法上。当应用在类上时,该类所有public
方法都将拥有该注解的事务属性。REQUIRED
表示如果当前存在事务,则加入该事务;如果不存在,则创建一个新事务。RuntimeException
或Error
时才会回滚事务。对于受检异常(Checked Exception),它不会回滚。我们可以通过rollbackFor
属性来改变这个行为。@Service
public class BankService {
@Autowired
private AccountRepository accountRepository;
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = Exception.class)
public void transfer(Long fromAccountId, Long toAccountId, double amount) throws Exception {
// 1. 扣款
Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow();
if (fromAccount.getBalance() < amount) {
throw new Exception("Insufficient balance");
}
fromAccount.setBalance(fromAccount.getBalance() - amount);
accountRepository.save(fromAccount);
// 模拟异常
if (amount > 1000) {
throw new RuntimeException("Transfer failed!");
}
// 2. 存款
Account toAccount = accountRepository.findById(toAccountId).orElseThrow();
toAccount.setBalance(toAccount.getBalance() + amount);
accountRepository.save(toAccount);
}
}
代码解释:
transfer
方法被@Transactional
注解。这意味着整个方法将在一个事务中执行。RuntimeException
,所有已经完成的数据库操作(如扣款)都将被回滚,数据库将恢复到方法调用之前的状态。rollbackFor = Exception.class
指定了即使是受检异常也会触发回滚。本章我们掌握了在Spring Boot中进行数据访问的关键技术。我们学习了如何使用Spring Data JPA快速实现数据操作,以及如何通过MyBatis获得对SQL的完全控制。此外,我们还探讨了多数据源配置、连接池优化和声明式事务管理等企业级应用中必不可少的实践。有了这些知识,我们就可以构建出高效、健壮的数据持久层。