ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] # 简介 Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率! > spring data jpa让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现 ~~~ <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>2.2.1.RELEASE</version> </dependency> <!-- 钱相关 --> <dependency> <groupId>org.joda</groupId> <artifactId>joda-money</artifactId> <version>1.0.1</version> </dependency> <!-- 类型映射相关 --> <dependency> <groupId>org.jadira.usertype</groupId> <artifactId>usertype.core</artifactId> <version>6.0.1.GA</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <!-- <version>5.1.48</version>--> <version>8.0.18</version> </dependency> ~~~ ![](https://img.kancloud.cn/8a/3e/8a3e71d9f6108395fd3a1a36211a6894_1428x910.png) # 配置 ~~~ spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.properties.hibernate.hbm2ddl.auto=create spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect #spring.jpa.show-sql=true #SQL 输出 spring.jpa.properties.hibernate.show_sql=true #format ⼀下 SQL 进⾏输出 spring.jpa.properties.hibernate.format_sql=true #显示绑定参数 logging.level.org.hibernate.type.descriptor.sql.BasicBinder=trace ~~~ `hibernate.hbm2ddl.auto` 参数的作⽤主要⽤于:⾃动创建、更新、验证数据库表结构,有四个值 * create:每次加载 Hibernate 时都会删除上⼀次⽣成的表,然后根据 model 类再重新来⽣成新表,哪怕 两次没有任何改变也要这样执⾏,这就是导致数据库表数据丢失的⼀个重要原因。 * `create-drop`:每次加载 Hibernate 时根据 model 类⽣成表,但是 sessionFactory⼀关闭,表就⾃动删除。 * update:最常⽤的属性,第⼀次加载 Hibernate 时根据 model 类会⾃动建⽴起表的结构(前提是先建⽴ 好数据库),以后加载 Hibernate 时根据 model 类⾃动更新表结构,即使表结构改变了,但表中的⾏仍然存在,不会删除以前的⾏。要注意的是当部署到服务器后,表结构是不会被马上建⽴起来的,是要等应⽤第⼀次运⾏起来后才会。 * validate :每次加载 Hibernate 时,验证创建数据库表结构,只会和数据库中的表进⾏⽐较,不会创建 新表,但是会插⼊新值 其中 * dialect:主要是指定⽣成表名的存储引擎为 InnoDB * show-sql: 是否在⽇志中打印出⾃动⽣成的 SQL,⽅便调试的时候查看 # 创建实体配置 ~~~ @Entity @Table(name = "user") @Data public class User { //主键 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; //@Column(nullable = false, unique = true) private String username; } ~~~ * `@Entity(name="EntityName")`必须,⽤来标注⼀个数据库对应的实体,数据库中创建的表名默 认和类名⼀致。其中,name 为可选,对应数据库中⼀个表,使⽤此注解标记 Pojo 是⼀个 JPA 实体。 * `@Table(name="",catalog="",schema="")` 可选,⽤来标注⼀个数据库对应的实体,数据库 中创建的表名默认和类名⼀致。通常和 @Entity 配合使⽤,只能标注在实体的 class 定义处,表示实体 对应的数据库表的信息。 * @Id 必须,@Id 定义了映射到数据库表的主键的属性,⼀个实体只能有⼀个属性被映射为主键。 * `@GeneratedValue(strategy=GenerationType,generator="")` 可选,strategy: 表示主键 ⽣成策略,有 AUTO、INDENTITY、SEQUENCE 和 TABLE 4 种,分别表示让 ORM 框架⾃动选择, generator: 表示主键⽣成器的名称。 * `@Column(name = "user_code", nullable = false, length=32)` 可选,@Column 描 述了数据库表中该字段的详细定义,这对于根据 JPA 注解⽣成数据库表结构的⼯具。name: 表示数据库 表中该字段的名称,默认情形属性名称⼀致;nullable: 表示该字段是否允许为 null,默认为 true; * unique: 表示该字段是否是唯⼀标识,默认为 false;length: 表示该字段的⼤⼩,仅对 String 类型的字段 有效。 * @Transient 可选,@Transient 表示该属性并⾮⼀个到数据库表的字段的映射,ORM 框架将忽略该 属性。 * @Enumerated 可选,使⽤枚举的时候,我们希望数据库中存储的是枚举对应的 String 类型,⽽不是 枚举的索引值,需要在属性上⾯添加 @Enumerated(EnumType.STRING) 注解 # 基本查询 基本查询也分为两种,一种是spring data默认已经实现,一种是根据查询的方法来自动解析成SQL。 ## 预先生成方法 spring data jpa 默认预先生成了一些基本的CURD的方法,例如:增、删、改等等 1. 继承JpaRepository ~~~ @Repository public interface UserRepository extends JpaRepository<User, Long> {} ~~~ 2. 默认方法 ![](https://img.kancloud.cn/1b/83/1b830d58ad591462b27d8a05251191ff_1922x1634.png) ## 自定义简单查询 自定义的简单查询就是根据方法名来自动生成SQL,主要的语法是`findXXBy`,`readAXXBy`,`queryXXBy`,`countXXBy`,`getXXBy`后面跟属性名称: ~~~ User findByUserName(String userName); ~~~ 也使用一些加一些关键字`And`、`Or` ~~~ User findByUserNameOrEmail(String username, String email); ~~~ 修改、删除、统计也是类似语法 ~~~ Long deleteById(Long id); Long countByUserName(String userName) ~~~ 修改、删除、统计也是类似语法 ~~~ Long deleteById(Long id); Long countByUserName(String userName) ~~~ 基本上SQL体系中的关键词都可以使用,例如:`LIKE`、`IgnoreCase`、`OrderBy`。 ~~~ List<User> findByEmailLike(String email); User findByUserNameIgnoreCase(String userName); List<User> findByUserNameOrderByEmailDesc(String email); ~~~ **具体的关键字,使用方法和生产成SQL如下表所示** | Keyword | Sample | JPQL snippet | | --- | --- | --- | | And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 | | Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 | | Is,Equals | findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 | | Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 | | LessThan | findByAgeLessThan | … where x.age < ?1 | | LessThanEqual | findByAgeLessThanEqual | … where x.age ⇐ ?1 | | GreaterThan | findByAgeGreaterThan | … where x.age > ?1 | | GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 | | After | findByStartDateAfter | … where x.startDate > ?1 | | Before | findByStartDateBefore | … where x.startDate < ?1 | | IsNull | findByAgeIsNull | … where x.age is null | | IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null | | Like | findByFirstnameLike | … where x.firstname like ?1 | | NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 | | StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) | | EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) | | Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) | | OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc | | Not | findByLastnameNot | … where x.lastname <> ?1 | | In | findByAgeIn(Collection ages) | … where x.age in ?1 | | NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 | | TRUE | findByActiveTrue() | … where x.active = true | | FALSE | findByActiveFalse() | … where x.active = false | | IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) | # jdk9 如果是jdk9,执行报错如下: ![](https://img.kancloud.cn/22/3e/223e808f120a66cf774ff7a07808a9f6_812x87.png) 原因: 缺少相应的jar 解决方案: 手动导入对应的maven坐标 ~~~ <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.0</version> </dependency> ~~~ # 其他 ## 使用枚举 使用枚举的时候,我们希望数据库中存储的是枚举对应的String类型,而不是枚举的索引值,需要在属性上面添加`@Enumerated(EnumType.STRING)`注解 ~~~ @Enumerated(EnumType.STRING) @Column(nullable = true) private UserType type; ~~~ ## 不需要和数据库映射的属性 正常情况下我们在实体类上加入注解`@Entity`,就会让实体类和表相关连如果其中某个属性我们不需要和数据库来关联只是在展示的时候做计算,只需要加上`@Transient`属性既可。 ~~~ @Transient private String userName; ~~~ ## 创建配置类 ~~~ import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; @Order(Ordered.HIGHEST_PRECEDENCE)//定义组件的加载顺序,这里为最高级 @Configuration//表明这是一个配置类 @EnableTransactionManagement(proxyTargetClass = true)//启用JPA的事物管理 @EnableJpaRepositories(basePackages = "com.example.springbootjpa.repository")//启动JPA资源库并设置接口资源库的位置 @EntityScan(basePackages = "com.example.springbootjpa.pojo")//实体类位置 public class JpaConfiguration { /** * @Description: 这里说明为什么要声明一个PersistenceExceptionTranslationPostProcessor 的Bean对象,引用Spring官方文档的一句话: * (1)scanned by Spring component-scanning * (2)catch platformspecific exceptions and rethrow them as one of Spring’s unified unchecked exceptions But if you’re using Hibernate contextual sessions and not a Hibernate template,how can the exception translation take place? 翻译过来就是:@Repository有两作用: (1):用于被容器扫描: (2):捕获平台特定的异常并将它们重新抛出,作为Spring的一个未检查的异常。(用于事务的管理,例如捕获异常回滚) 但是,如果您使用的是Hibernate contextual sessions上下文会话而不是Hibernate template,那么异常转换是如何发生的呢? 那么,这就是配置这个类的作用。 * @return */ @Bean PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor(){ return new PersistenceExceptionTranslationPostProcessor(); } } ~~~ ## @EntityScan和@EnableJpaRepositories `@EntityScan`用来扫描和发现指定包及其子包中的`Entity`定义。其用法如下: ~~~ @EntityScan(basePackages = {"com.department.entities","come.employee.entities"}) ~~~ 如果多处使用@EntityScan,它们的basePackages集合能覆盖所有被Repository使用的Entity即可,集合有交集也没有关系。但是如果不能覆盖被Repository使用的Entity,应用程序启动是会出错,比如: > Not a managed type: com.customer.entities.Customer --- @EnableJpaRepositories 1. **basePackage用于配置扫描Repositories所在的package及子package** @EnableJpaRepositories用来扫描和发现指定包及其子包中的`Repository`定义。其用法如下: ~~~ @EnableJpaRepositories(basePackages = {"com.department.repositories","come.employee.repositories"}) ~~~ 如果多处使用@EnableJpaRepositories,它们的basePackages集合不能有交集,并且要能覆盖所有需要的Repository定义。 如果有交集,相应的Repository会被尝试反复注册,从而遇到如下错误: ~~~ The bean ‘OrderRepository’, defined in xxx, could not be registered. A bean with that name has already been defined in xxx and overriding is disabled. ~~~ 如果不能覆盖所有需要的Repository定义,会遇到启动错误: ~~~ Parameter 0 of method setCustomerRepository in com.service.CustomerService required a bean of type ‘come.repo.OrderRepository’ that could not be found. ~~~ 2. **basePackageClasses指定 Repository类** 3. **includeFilters过滤器,该过滤区采用ComponentScan的过滤器类** 4. **excludeFilters不包含过滤器** 5. **repositoryImplementationPostfix实现类追加的尾部**, 比如ShopRepository,对应的为ShopRepositoryImpl 6. **namedQueriesLocation:named SQL存放的位置**,默认为META-INF/jpa-named-queries.properties 7. **queryLookupStrategy构建条件查询的策略** 我们配置了多种Query,Spring Data JPA 如何来查找这些 Query 呢? 包含三种方式CREATE,USE\_DECLARED\_QUERY,CREATE\_IF\_NOT\_FOUND * CREATE:尝试从查询⽅法名构造特定于存储的查询。⼀般的⽅法是从⽅法名中删除⼀组已知的前缀, 并解析⽅法的其余部分。 * `USE_DECLARED_QUERY`:尝试查找已声明的查询,如果找不到,则抛出异常。查询可以通过某个地 ⽅的注释定义,也可以通过其他⽅式声明。 * `CREATE_IFNOTFOUND`(默认):CREATE 和 `USE_DECLARED_QUERY` 的组合,它⾸先查找⼀个 已声明的查询,如果没有找到已声明的查询,它将创建⼀个⾃定义⽅法基于名称的查询。它允许通过⽅ 法名进⾏快速查询定义,还可以根据需要引⼊声明的查询来定制这些查询调优。 8. **repositoryFactoryBeanClass: 指定Repository的工厂类** 9. **entityManagerFactoryRef: 实体管理工厂引用名称,对应到@Bean注解对应的方法** 10. **transactionManagerRef: 事务管理工厂引用名称,对应到@Bean注解对应的方法** **完整的配置** ~~~ @EnableJpaRepositories( // basePackages 支持多包扫描,用文本数组的形式就可以 // 比如这样 {"com.simply.zuozuo.repo","com.simply.zuozuo.mapper"} basePackages = { "com.simply.zuozuo.repo" }, value = {}, // 指定里面的存储库类 basePackageClasses = {}, // 包含的过滤器 includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Repository.class) }, // 不包含的过滤器 excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Service.class), @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class) }, // 通过什么后缀来命名实现类,比如接口A的实现,名字叫AImpl repositoryImplementationPostfix = "Impl", // named SQL存放的位置,默认为META-INF/jpa-named-queries.properties namedQueriesLocation = "", // 枚举中有三个值, // CREATE_IF_NOT_FOUND,先搜索用户声明的,不存在则自动构建 // USE_DECLARED_QUERY,用户声明查询 // CREATE,按照接口名称自动构建查询 queryLookupStrategy = QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND, // 指定Repository的工厂类 repositoryFactoryBeanClass = JpaRepositoryFactoryBean.class, // 指定Repository的Base类 repositoryBaseClass = DefaultRepositoryBaseClass.class, // 实体管理工厂引用名称,对应到@Bean注解对应的方法 entityManagerFactoryRef = "entityManagerFactory", // 事务管理工厂引用名称,对应到@Bean注解对应的方法 transactionManagerRef = "transactionManager", // 是否考虑嵌套存储库 considerNestedRepositories = false, // 开启默认事务 enableDefaultTransactions = true ) public class JPAConfig { } ~~~ # 索引 ~~~java @Entity(name = "t_user") // 定义数据库表名称 @Table(indexes = { // 定义数据库索引 // 唯一索引。 @Index(name = "ux_user_login_name", columnList = "loginName", unique = true), // 非唯一索引。 @Index(name = "idx_user_age", columnList = "age"), }) public class User { ~~~ @Table 里面定义了这个表的索引, 一个 @Index 注解定义了一个索引, name 属性表示数据库表中索引的名称, columnList 表示对应的 java 属性名称, unique = true 表示此索引是唯一索引。 比如上面的 `@Index(name = "ux_user_login_name", columnList = "loginName", unique = true)` 表示对 loginName 属性所对应的字段(映射到数据库表中应该是 login\_name 字段)建立唯一索引,索引名为ux\_user\_login\_name columnList 中可以放多个java属性,中间用逗号隔开,表示联合索引,如:`@Index(name = "idx_user_age_name", columnList = "age,loginName")` 表示建立 age 与 login\_name 字段的联合索引 注意:** java 属性名都是驼峰命名法(如 loginName),而数据库表字段都是下划线命名法(如 login\_name)**, **JPA会自动根据java属性名的驼峰命名法映射成数据库表字段的下划线命名法**