Java持久化技术是Java开发中的重要组成部分,它主要用于将对象数据持久化到数据库中,以及从数据库中查询和恢复对象数据。在Java持久化技术领域,Java Persistence API (JPA) 和 Spring Data JPA 是两个非常流行的框架。
Java Persistence API (JPA) 是一种基于 ORM (Object-Relational Mapping) 技术的 Java EE 规范。它主要用于将 Java 对象映射到关系型数据库中,以便于对数据进行持久化操作。
JPA 主要由三个部分组成,分别是 Entity、EntityManager 和 Query。其中 Entity 用于描述 Java 对象和数据库表之间的映射关系;EntityManager 用于管理实体对象的生命周期和完成实体对象与数据库之间的操作;Query 用于查询数据。
JPA 支持多种底层实现,如 Hibernate、EclipseLink 等。在使用时,只需要引入相应的实现框架即可。 总结如下:
Spring Data JPA 是 Spring 框架下的一个模块,是基于 JPA 规范的上层封装,旨在简化 JPA 的使用。
Spring Data JPA 提供了一些常用的接口,如 JpaRepository、JpaSpecificationExecutor 等,这些接口包含了很多常用的 CRUD 操作方法,可直接继承使用。同时,Spring Data JPA 还提供了基于方法命名规范的查询方式,可以根据方法名自动生成相应的 SQL 语句,并执行查询操作。这种方式可以大大减少编写 SQL 语句的工作量。
除了基础的 CRUD 操作外,Spring Data JPA 还提供了一些高级功能,如分页、排序、动态查询等。同时,它也支持多种数据库,如 MySQL、PostgreSQL、Oracle 等。 总结如下:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
在项目中创建实体类,用于映射数据库表和列。实体类需要使用@Entity注解进行标记,并且需要指定主键和自动生成策略,例如:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// ...
// 省略 getter 和 setter 方法
}
在项目中创建 Repository 接口,用于定义数据访问方法。Repository 接口需要继承自java JpaRepository接口,并且需要使用java @Repository注解进行标记,例如:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByName(String name);
}
在示例中,我们定义了一个名为 UserRepository 的接口,它继承自 JpaRepository 接口,泛型参数分别为实体类型和主键类型,并且新增了一个自定义查询方法 findByName。
编写示例代码并运行应用程序,以验证 Spring Data JPA 的功能和使用方法。示例代码可以是简单的控制台程序,也可以是 Web 应用程序。下面是一个基于 Spring Boot 的 Web 应用程序的示例代码:
@SpringBootApplication
public class Application implements CommandLineRunner {
@Autowired
private UserRepository userRepository;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) throws Exception {
User user = new User();
user.setName("Alice");
user.setEmail("[email protected]");
userRepository.save(user);
User savedUser = userRepository.findByName("Alice");
System.out.println(savedUser);
}
}
在示例代码中,我们使用 @SpringBootApplication 注解标记了应用程序入口类,并且在 main 方法中启动了应用程序。CommandLineRunner 接口的 run 方法用于定义初始化逻辑,在示例中我们创建了一个名为 Alice 的用户,并将其保存到数据库中,随后使用 findByName 方法查询并输出该用户信息。
Entity 注解
@Entity 注解用于标记实体类,表示该类会被映射到数据库中的一个表。示例代码:
@Entity
public class User {
// 省略属性和方法
}
Table 注解
@Table 注解用于标注实体类与数据库表之间的映射关系,并可以指定表的名称、唯一约束等信息。示例代码:
@Entity
@Table(name = "user")
public class User {
// 省略属性和方法
}
Column 注解
@Column 注解用于标注实体类属性与数据表字段之间的映射关系,并可以指定字段名称、长度、精度等信息。示例代码:
@Entity
@Table(name = "user")
public class User {
@Id
private Long id;
@Column(name = "user_name", length = 20, nullable = false)
private String userName;
// 省略其他属性和方法
}
Id 注解
@Id 注解用于标注实体类属性作为主键,通常与 @GeneratedValue 注解一起使用指定主键生成策略。示例代码:
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// 省略其他属性和方法
}
GeneratedValue注解
@GeneratedValue 注解用于指定主键生成策略,通常与 @Id 注解一起使用。示例代码:
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 省略其他属性和方法
}
关系映射通常包括一对一、一对多和多对多等关系。在 Spring Data JPA 中,可以使用 @OneToOne、@OneToMany 和 @ManyToMany 注解来标注关系映射。这些注解通常与 @JoinColumn 注解一起使用,用于指定关联的外键列。
示例代码:
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Address> addresses;
// 省略其他属性和方法
}
@Entity
@Table(name = "address")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
// 省略其他属性和方法
}
在上例中,User 和 Address 之间是一对多的关系,所以在 User 实体类中使用了 @OneToMany 注解,在 Address 实体类中使用了 @ManyToOne 注解。mappedBy 属性用于指定关联的属性名称,这里是 user,表示 Address 实体类中的 user 属性与 User 实体类中的 addresses 属性相对应。cascade 属性表示级联操作,这里使用 CascadeType.ALL 表示在删除 User 实体时同时删除其关联的所有 Address 实体。@JoinColumn 注解用于指定外键名称,这里是 user_id,表示 Address 表中的 user_id 列与 User 表中的主键相对应。
Repository 接口是 Spring Data JPA 的核心接口之一,它提供了基本的增删改查方法和自定义查询方法,以及分页和排序等功能。在使用时需要继承 Repository 接口并指定对应的实体类和主键类型。示例代码:
public interface UserRepository extends Repository<User, Long> {
// 省略基本增删改查方法和自定义查询方法
}
在继承 Repository 接口后,会默认提供基本的增删改查方法,无需额外的代码实现即可使用。常用的方法如下:
方法名 | 描述 |
---|---|
T save(T entity) | 保存实体对象 |
Iterable saveAll(Iterable entities) | 批量保存实体对象 |
Optional findById(ID id) | 根据主键获取实体对象 |
boolean existsById(ID id) | 判断是否存在特定主键的实体对象 |
Iterable findAll() | 获取所有实体对象 |
Iterable findAllById(Iterable ids) | 根据主键批量获取实体对象 |
long count() | 获取实体对象的数量 |
void deleteById(ID id) | 根据主键删除实体对象 |
void delete(T entity) | 删除实体对象 |
void deleteAll(Iterable extends T> entities) | 批量删除实体对象 |
示例代码:
public interface UserRepository extends Repository<User, Long> {
// 保存用户
User save(User user);
// 根据主键获取用户
Optional<User> findById(Long id);
// 获取所有用户
Iterable<User> findAll();
// 根据主键删除用户
void deleteById(Long id);
}
在 Repository 接口中可以定义自定义查询方法,实现按照指定规则查询数据。Spring Data JPA 支持三种方式定义自定义查询方法:方法名称查询、参数设置查询、使用 @Query 注解查询。
方法名称查询
方法名称查询是 Spring Data JPA 中最简单的一种自定义查询方法,并且不需要额外的注解或 XML 配置。它通过方法名来推断出查询的条件,例如以 findBy 开头的方法表示按照某些条件查询,以 deleteBy 开头的方法表示按照某些条件删除数据。示例代码:
public interface UserRepository extends Repository<User, Long> {
// 根据用户名查询用户
User findByUserName(String userName);
// 根据年龄查询用户列表
List<User> findByAge(Integer age);
// 根据用户名和密码查询用户
User findByUserNameAndPassword(String userName, String password);
// 根据主键和用户名删除用户
void deleteByIdAndUserName(Long id, String userName);
}
查询参数设置
除了方法名称查询外,还可以使用参数设置方式进行自定义查询。它通过在方法上使用 @Query 注解来指定查询语句,然后使用 @Param 注解来指定方法参数与查询语句中的参数对应关系。示例代码:
public interface UserRepository extends Repository<User, Long> {
// 根据用户名查询用户
@Query("SELECT u FROM User u WHERE u.userName = :userName")
User findByUserName(@Param("userName") String userName);
// 根据用户名和密码查询用户
@Query("SELECT u FROM User u WHERE u.userName = :userName AND u.password = :password")
User findByUserNameAndPassword(@Param("userName") String userName, @Param("password") String password);
}
使用@Query注解
在自定义查询方法时,还可以使用 @Query 注解直接指定查询语句。@Query 注解的 value 属性表示查询语句,可以使用占位符 ?1、?2 等表示方法参数。示例代码:
public interface UserRepository extends Repository<User, Long> {
// 根据用户名查询用户
@Query(value = "SELECT * FROM user WHERE user_name = ?1", nativeQuery = true)
User findByUserName(String userName);
// 根据用户名和密码查询用户
@Query(value = "SELECT * FROM user WHERE user_name = ?1 AND password = ?2", nativeQuery = true)
User findByUserNameAndPassword(String userName, String password);
}
在查询数据时,经常需要对结果进行排序和分页操作。Spring Data JPA 提供了 Sort 和 Pageable 两个类来实现排序和分页功能。
Sort 类表示排序规则,可以使用 Sort.by() 静态方法创建实例,并指定排序属性和排序方向。常用方法如下:
方法名 | 描述 |
---|---|
static Sort by(Sort.Order… orders) | 根据排序规则创建 Sort 实例 |
static Sort.Order by(String property) | 根据属性升序排序 |
static Sort.Order by(String property, Sort.Direction direction) | 根据属性排序 |
示例代码:
public interface UserRepository extends Repository<User, Long> {
// 根据年龄升序查询用户列表
List<User> findByOrderByAgeAsc();
// 根据年龄降序分页查询用户列表
Page<User> findBy(Pageable pageable);
}
Pageable 类表示分页信息,可以使用 PageRequest.of() 静态方法创建实例,并指定页码、每页数据量和排序规则。常用方法如下:
方法名 | 描述 |
---|---|
static PageRequest of(int page, int size, Sort sort) | 创建分页信息实例 |
static PageRequest of(int page, int size, Sort.Direction direction, String… properties) | 创建分页信息实例 |
示例代码:
public interface UserRepository extends Repository<User, Long> {
// 根据年龄降序分页查询用户列表
Page<User> findBy(Pageable pageable);
}
// 使用
Pageable pageable = PageRequest.of(0, 10, Sort.by("age").descending());
Page<User> page = userRepository.findBy(pageable);
List<User> userList = page.getContent();
在 Spring Data JPA 中,使用 update 和 delete 语句需要使用 @Modifying 注解标注,并且需要添加 @Transactional 注解开启事务。需要注意的是,@Modifying 注解只支持 DML 语句。
示例代码:
public interface UserRepository extends Repository<User, Long> {
// 更新用户密码
@Modifying
@Transactional
@Query("UPDATE User u SET u.password = :password WHERE u.id = :id")
void updatePasswordById(@Param("id") Long id, @Param("password") String password);
// 删除年龄大于等于 age 的用户
@Modifying
@Transactional
@Query("DELETE FROM User u WHERE u.age >= :age")
void deleteByAgeGreaterThanEqual(@Param("age") Integer age);
}
在某些情况下,需要执行原生的 SQL 查询语句。Spring Data JPA 提供了 @Query 注解来支持使用原生 SQL 查询数据。在 @Query 注解中设置 nativeQuery=true 即可执行原生 SQL 语句。
以下示例代码演示了如何使用原生 SQL 查询 age 大于等于 18 的用户。
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM user WHERE age >= ?1", nativeQuery = true)
List<User> findByAgeGreaterThanEqual(Integer age);
}
// 使用
userRepository.findByAgeGreaterThanEqual(18);
数据库初始化
在使用 Spring Data JPA 进行开发时,使用 @Entity 标注实体类,当应用程序启动时,JPA 会自动检测实体类上的注解,并在数据库中创建对应的表。
以下示例代码演示了如何在 Spring Boot 应用程序中使用 JPA 创建表。
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String name;
@Column
private Integer age;
// 省略 getter 和 setter 方法
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 添加用户
public User addUser(User user) {
return userRepository.save(user);
}
}
在上述示例代码中,使用 @Entity 标注实体类,并在 @Table 注解中指定表名。当应用程序启动时,JPA 会自动创建名为 user 的表。使用 JpaRepository 提供的方法进行增删改查等操作。
数据库升级
当数据库表结构需要修改时,可以使用 Flyway、Liquibase 等工具进行数据库升级。其中,Flyway 是一款轻量级的数据库迁移工具,它可以很好地与 Spring Boot 集成。
以下示例代码演示了如何在 Spring Boot 应用程序中使用 Flyway 进行数据库升级:
<dependency>
<groupId>org.flywaydbgroupId>
<artifactId>flyway-coreartifactId>
<version>7.6.3version>
dependency>
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
# Flyway 数据库升级相关配置
spring.flyway.locations=classpath:/db/migration
spring.flyway.baselineOnMigrate=true
-- V1__create_user_table.sql
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- V2__add_address_column_to_user_table.sql
ALTER TABLE `user` ADD `address` VARCHAR(255) NULL AFTER `age`;
@SpringBootTest
class ApplicationTests {
@Test
void contextLoads() {
}
}
在实际应用中,有时需要使用多个数据源。Spring Boot 提供了 @ConfigurationProperties、@Primary、@Qualifier 等注解来支持多数据源配置。以下示例代码演示了如何在 Spring Boot 应用程序中配置多数据源。
在 application.properties 文件中配置两个数据源的连接信息
# 数据源一
spring.datasource.one.url=jdbc:mysql://localhost:3306/test1
spring.datasource.one.username=root
spring.datasource.one.password=123456
# 数据源二
spring.datasource.two.url=jdbc:mysql://localhost:3306/test2
spring.datasource.two.username=root
spring.datasource.two.password=123456
创建两个数据源的配置类
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.one")
public class DataSourceOneConfig {
private String url;
private String username;
private String password;
// 省略 getter 和 setter 方法
@Bean
public DataSource dataSourceOne() {
return DataSourceBuilder.create()
.url(url)
.username(username)
.password(password)
.build();
}
}
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.two")
public class DataSourceTwoConfig {
private String url;
private String username;
private String password;
// 省略 getter 和 setter 方法
@Bean
public DataSource dataSourceTwo() {
return DataSourceBuilder.create()
.url(url)
.username(username)
.password(password)
.build();
}
}
在 Service 或 Repository 中指定要使用的数据源
@Service
public class UserService {
@Autowired
@Qualifier("dataSourceOne")
private DataSource dataSourceOne;
@Autowired
@Qualifier("dataSourceTwo")
private DataSource dataSourceTwo;
public void addUser(User user) {
try (Connection connection = dataSourceOne.getConnection();
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO user (name, age) VALUES (?, ?)"
)
) {
statement.setString(1, user.getName());
statement.setInt(2, user.getAge());
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
在上述示例代码中,使用 @ConfigurationProperties 注解将数据源的连接信息和配置类绑定。使用 @Qualifier 和 @Autowired 注解指定要使用的数据源。在 Service 或 Repository 中通过 DataSource.getConnection() 获取连接,手动执行 SQL 语句。
审计日志和版本控制是企业级应用程序常见的需求,Spring Data JPA 提供了 @CreatedBy、@CreatedDate、@LastModifiedBy、@LastModifiedDate 和 @Version 等注解来支持审计日志和版本控制功能。以下示例代码演示了如何在实体类中使用 Spring Data JPA 提供的注解实现审计日志和版本控制。
@Entity
@Table(name = "user")
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String name;
@Column
private Integer age;
@CreatedDate
@Column(name = "create_time", nullable = false, updatable = false)
private LocalDateTime createTime;
@CreatedBy
@Column(name = "create_by", nullable = false, updatable = false)
private String createBy;
@LastModifiedDate
@Column(name = "update_time", nullable = false)
private LocalDateTime updateTime;
@LastModifiedBy
@Column(name = "update_by", nullable = false)
private String updateBy;
@Version
@Column(name = "version")
private Long version;
// 省略 getter 和 setter 方法
}
在上述示例代码中,使用 @CreatedDate 和 @CreatedBy 注解标注创建时间和创建人属性,并设置 nullable=false 和 updatable=false 属性不允许为空和更新。使用 @LastModifiedDate 和 @LastModifiedBy 注解标注修改时间和修改人属性,并设置 nullable=false 属性不允许为空。使用 @Version 注解标注版本号属性。
在配置类中启用审计功能:
@Configuration
@EnableJpaAuditing
public class JpaConfig {
}
这样就可以在增删改查操作中自动记录审计日志和版本信息。
Spring Data JPA 适用于需求较为简单的 CRUD 操作的项目,特别是对于初学者来说,使用 Spring Data JPA 可以很快速的上手。对于一些需要进行关联操作的复杂查询场景,或者需要特定的 SQL 语句实现的场景,可以考虑使用 MyBatis 或者直接使用 Hibernate。但对于大多数项目而言,使用 Spring Data JPA 已经能够很好地满足需求。