Spring Data JPA的使用


 

spring data简介

spring data是spring的一个子项目,是spring家族成员之一,旨在简化数据访问层的开发,给操作数据库(关系数据库sql、非关系数据库nosql)提供了一系列组件,常用的比如

  • spring data jpa:整合关系数据库
  • spring data redis:整合redis
  • spring data mongodb:整合mongodb
  • spring data elasticsearch:整合es

 

spring data jpa简介

  • Java Persistence API,spring data jpa是spring data系列组件之一,旨在简化对关系数据库的操作,可作为 mybatis、hibernate 外的另一种选择。
  • 包含了 hibernate 的很多功能,比如实体类生成表的正向工程,比如 @Entity、@Table、@Id、@GeneratedValue、@ManyToMany、@JoinTable 等注解。
  • 封装了dao层的通用接口,提供了许多通用方法,可通过方法命名约定不写单表操作的简单sql,简化了数据访问层的开发,大大提高了开发效率。
  • 提供的关联查询、级联操作了解即可,不推荐使用,因为容易踩坑、难以维护。

官方文档:https://docs.spring.io/spring-data/jpa/reference/index.html
github:https://github.com/spring-projects/spring-data-jpa

 

springboot整合jpa

依赖

创建项目时勾选 SQL -> Spring Data JPA、数据库驱动

<!-- 已包含 hibernate-core -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

jpa 的依赖中已包含 hibernate 的核心依赖。

 

yml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    # createDatabaseIfNotExist=true 数据库不存在时自动创建
    url: jdbc:mysql://localhost:3306/mall?serverTimezone=Asia/Shanghai&allowMultiQueries=true&createDatabaseIfNotExist=true
    username: root
    password: password

  jpa:
    #hibernate正向工程,根据实体类生成对应的数据表
    hibernate:
      ddl-auto: update
    #打印sql的日志
    show-sql: true

spring.jpa.hibernate.ddl-auto 可用的值

  • none:默认值,应用启动时不自动进行数据表DDL操作。
  • validate:应用启动时自动检查数据表定义与java实体类是否匹配,不匹配时直接启动失败。
  • create:应用启动时自动先删除再重建数据表。
  • create-drop:应用启动时自动先删除再重建数据表,应用停止(正常停止)时自动删除数据表。
  • update(推荐):根据java实体类更新对应的数据表定义,为了数据安全、避免误操作,只会在现有的数据表结构上进行新增(列、约束、默认值等),不会删除已有的列、约束。

 

实体类
import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.math.BigDecimal;

@Data
@Entity  //标识为实体类
@Table(name = "t_user")  //可指定表名,默认取类名的snake写法作为表名,eg. GoodsInfo => goods_info
public class User implements Serializable {

    @Id  //标识主键字段
    @GeneratedValue  //指定主键生成策略,strategy默认为AUTO 自动根据数据库类型选择合适的主键生成策略
    private Long id;

    @Column(unique = true, nullable = false)  //unique是加唯一索引约束,默认false;nullable是允许空值,默认true
    private String username;

    @Column(name = "name")  //指定数据表列名,未指定时默认取变量名的snake写法作为列名,eg. realName=> real_name
    private String realName;

    @Column(length = 11)  //String类型字段可以使用 length 指定字符串最大长度,默认255,会自动根据length选择varchar、text等合适的数据库字段类型
    private String tel;

    private String password;

    @Column(precision = 15, scale = 2)  //decimal类型必须指定位数,precision是总位数,scale是小数位数
    private BigDecimal balance;

    @Column(columnDefinition = "VARCHAR(50) DEFAULT 'normal'")  //可以自行指定列定义,columnDefinition 会被拼接到DDL中
    private String status;

    private Date createTime;

    private Date updateTime;

}

@GeneratedValue 的 strategy 属性指定主键生成策略

  • IDENTITY:使用数据库的主键自增,会给列定义加 auto_increment,只适用于支持主键自增的数据库,Oracle 不支持此策略。
  • TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。
  • SEQUENCE:通过序列产生主键,通过 @SequenceGenerator 注解指定序列名,mysql 不支持这种方式。
  • AUTO:默认值,自动根据数据库类型选择合适的主键生成策略,mysql 是选择主键自增,但不会给列定义添加 auto_increment。

 

DAO层

继承 JpaRepository 接口,JpaRepository 提供了通用的 crud 功能,可以自行补充所需方法

// 会自动作为bean放到spring容器中,不需要手动加 @Repository 之类的注解。泛型执行实体类、主键对应的java类型
public interface UserRepository extends JpaRepository<User, Long> {

    User findByUsername(String username);

    Optional<User> findByTel(String tel);

    User findByIdAndStatus(Long id, String status);

    User findByUsernameOrTel(String username, String tel);

    List<User> findByRealNameNull();

    List<User> findByRealNameNotNull();

    List<User> findByIdBetween(Long minId, Long maxId);

    List<User> findByIdLessThan(Long maxId);

    List<User> findByIdLessThanEqual(Long maxId);
    
    List<User> findByIdIn(List<Long> idList);

    List<User> findByStatusNotIn(List<String> statusList);

}

方法命名需遵守jpa规范:https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html

 

常用方法

// 新增或更新,持久化到数据库后会回填入参、返回值中的字段
// 暂时将实体对象保存到持久化上下文中(Persistence Context),在提交事务或显式调用flush()时才持久化到数据库
User save = userRepository.save(user);
List<User> saveAll = userRepository.saveAll(userList);

userRepository.deleteAll();
userRepository.deleteById(id);
userRepository.deleteAllById(idList);



Example<User> example = Example.of(User.builder().username("xxx").build());

boolean existsById = userRepository.existsById(id);
boolean exists = userRepository.exists(example);

long count = userRepository.count();
long count = userRepository.count(example );



Optional<User> byId = userRepository.findById(id);
List<User> allById = userRepository.findAllById(idList);

// 有多条匹配的记录时会抛出异常
Optional<User> xxx = userRepository.findOne(example);

List<User> all = userRepository.findAll();
List<User> all = userRepository.findAll(example);
// 排序。未指定排序方向时,默认为 Direction.ASC 升序
Sort sort = Sort.by("id");
Sort sort = Sort.by("id").descending();
Sort sort = Sort.by("id", "createTime");
Sort sort = Sort.by(Sort.Direction.DESC, "id", "createTime");
Sort sort = Sort.by(Sort.Order.by("id"), Sort.Order.by("createTime"));
Sort sort = Sort.by(Sort.Order.desc("id"), Sort.Order.asc("createTime"));
List<User> all = userRepository.findAll(sort);
List<User> all = userRepository.findAll(example, sort);



// 分页。page从0开始,0即第一页,未指定page时默认为0
Pageable pageable = Pageable.ofSize(20).withPage(0);
// 也可以使用子类 PageRequest,同时支持分页、排序
PageRequest pageRequest = PageRequest.of(0, 20);
PageRequest pageRequest = PageRequest.ofSize(20);
PageRequest pageRequest = PageRequest.of(0, 20, sort);

Page<User> all = userRepository.findAll(pageable);
Page<User> all = userRepository.findAll(example, pageable);

 

自定义sql

// 使用的是java实体类、实体类的字段名
@Query("select user from User user where user.tel = :tel and user.status = :status")
User findByTelAndStatus(@Param("tel") String tel, @Param("status") String status);

// select可以选取整个实体或单个字段,不能 select user.username, user.tel 这样选择多个字段,也不能使用通配符 *
@Query("select user.id from User user where user.tel = :tel and user.status = :status")
Long findIdByTelAndStatus(@Param("tel") String tel, @Param("status") String status);

// insert、update、delete 语句还需要加 @Modifying;调用 @Modifying 修饰的DAO层方法时,必须要在方法上加事务 @Transactional
@Modifying
@Query("update User user set user.username = :username, user.tel = :tel where user.id = :id")
void updateUsernameAndTelById(@Param("id") Long id, @Param("username") String username, @Param("tel") String tel);

 

参数绑定

jpa有2种参数绑定方式

  • 基于位置的参数绑定:使用?指定参数位置,?1 即引用形参表的第一个参数,?2 即引用第二个参数。增删参数个数、调整参数顺序时可能搞忘维护sql中的?顺序,容易留坑,不推荐使用此种方式。
  • 基于注解的参数绑定(推荐):使用jpa的 @Param 注解绑定参数名,sql中用冒号引用参数
@Query("select user from User user where user.username = ?1 and user.tel = ?2")
User findByUsernameAndStatus(String name, String tel);


@Query("select user from User user where user.username = :username and user.status = :status")
User findByUsernameAndStatus(@Param("username") String username, @Param("status") String status);

@Query("select user from User user where user.username = :username and user.status = :status")
User findByUsernameAndStatus(@Param("username") String x1, @Param("status") String x2);
// 约定方法名的方法,jpa默认使用基于位置的参数绑定
User findByUsernameAndStatus(String x1, String x2);

// @Param 需要搭配自定义sql使用,单独使用无效
@Query("select user from User user where user.username = :username and user.status = :status")
User findByUsernameAndStatus(@Param("username") String x1, @Param("status") String x2);


// spring4开始支持jdk8的参数名自动发现,可以省略jpa的@Param注解,直接引用参数名
// 不建议这样做,可能留坑,尽量用@Param显式绑定参数名
@Query("select user from User user where user.username = :username and user.status = :status")
User findByUsernameAndStatus(String username, String status);

// 此句方法中的 @Param 无效,实际使用的是基于位置的参数绑定
User findByUsernameAndStatus(@Param("username") String x1, @Param("status") String x2);

 

关联关系的表达

生成表时,jpa会自动把 @JoinColumn#name 指定的数据表字段 设置为外键;查询时,jpa会自动根据实体类的关联关系获取成员变量对应的实体。
 

一对一

eg. 一个用户对应一张会员卡,一张会员卡也只属于一个用户

@Entity
@Data
public class User implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    private String username;

    private String password;

    private String tel;

    private String address;

    @OneToOne
    @JoinColumn(name = "card_id")  //指定该实体类对应的外键列名
    private Card card;
    
}
@Data
@Entity
public class Card implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    private BigDecimal money;

    @OneToOne
    @JoinColumn(name = "user_id")  //此处可不要@JoinColumn
    @JsonIgnore  //放弃维护此字段
    private User user;

}

不使用@JsonIgnore会根据循环引用查询嵌套结果,使用@JsonIgnore后,该字段不会关联到对应的表,相当于一方放弃了关联关系。

此处User可以查到对应的Card,Card不能查到对应的User。

 

一对多

eg. 一个用户有多个订单,这些订单都属于同一个用户

@Entity
@Data
public class User implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    private String username;

    private String password;

    private String tel;

    private String address;

    @OneToMany(mappedBy = "user")  //mappedBy指定Order中的当前实体(User)对应的成员变量
    private List<Order> orderList;

}
@Data
@Entity
@Table(name = "tb_order")  //order和sql的排序关键字order冲突,不能使用order作为表名
public class Order implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    @ManyToOne
    @JoinColumn(name = "user_id")  //在多的一方指定外键列名
    @JsonIgnore  //放弃维护此字段
    private User user;

}

哪方是一就加@OneToMany,哪方是多就加@ManyToOne,To前面的单词代表当前实体类。

此处User可以查到对应的Order,Order不能查到所属的User。

 

多对多

eg. 一个学生有多个老师,一个老师也有多个学生

@Entity
@Data
public class Student implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    @ManyToMany  // @JoinTable指定中间表,name指定表名,joinColumns指定中间表中当前实体类对应的字段,inverseJoinColumns指定中间表中对方对应的字段
    @JoinTable(name = "teacher_student", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "teacher_id"))
    private List<Teacher> teacherList;

}
@Data
@Entity
public class Teacher implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    @ManyToMany
    @JoinTable(name = "teacher_student",joinColumns =@JoinColumn(name = "teacher_id"),inverseJoinColumns = @JoinColumn(name = "student_id"))
    @JsonIgnore  //放弃维护此字段
    private List<Student> studentList;

}

此处,学生可以查到自己的老师,老师不能查到自己的学生。

 

常见问题

1、关联查询使用懒加载时报错 LazyInitializationException - no Session

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.xxx.xxx.xxx, could not initialize proxy - no Session

原因:执行懒加载之前事务就提交了,数据库 session 已经关闭,导致执行懒加载时没有可用的数据库session。
解决方案:在 yml 中添加以下配置

#允许在无事务的情况下进行懒加载
spring.jpa.properties.hibernate.enable_lazy_load_no_trans: true
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Data JPA 是一个用于简化数据库访问的框架,它基于 JPA (Java Persistence API) 规范,提供了一种更简单、更高效的方式来访问和操作数据库。 下面是使用 Spring Data JPA 的一般步骤: 1. 添加依赖:在你的项目中,添加 Spring Data JPA 的依赖。你可以在 Maven 或 Gradle 配置文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> ``` 2. 配置数据库连接:在 `application.properties` 或 `application.yml` 文件中配置数据库连接信息,包括数据库 URL、用户名、密码等。 3. 创建实体类:创建与数据库表对应的实体类,并使用 JPA 注解来映射实体类与数据库表之间的关系。 ```java @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Integer age; // getters and setters } ``` 4. 创建 Repository 接口:创建一个继承自 `JpaRepository` 的接口,并通过方法命名规则或自定义查询方法来定义数据库操作。 ```java public interface UserRepository extends JpaRepository<User, Long> { List<User> findByAgeGreaterThan(Integer age); } ``` 5. 使用 Repository:在需要访问数据库的地方,注入 Repository 接口,并调用其中的方法进行数据库操作。 ```java @Service public class UserService { @Autowired private UserRepository userRepository; public List<User> getUsersByAgeGreaterThan(Integer age) { return userRepository.findByAgeGreaterThan(age); } } ``` 这只是 Spring Data JPA 的基本用法,你还可以使用更高级的特性,如分页、排序、复杂查询等。希望这些信息对你有帮助!如果你有其他问题,请随时提问。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值