1 什么是JPA
JPA全称Java Persistence API,可以通过注解或者XML描述【对象-关系表】之间的映射关系,并将实体对象持久化到数据库中。JPA的出现主要是为了简化持久层开发以及整合ORM技术,结束Hibernate、TopLink、JDO等ORM框架各自为营的局面。
JAP为我们提供了ORM映射元数据,JPA的API,JPQL查询语言等,但JPA仅仅是一种规范,也就是说JPA仅仅定义了一些接口,而接口是需要实现才能工作的。所以底层需要某种实现,而Hibernate就是实现了JPA接口的ORM框架。
2 什么是Hibernate框架
Hibernate是Java中的对象关系映射解决方案。对象关系映射或ORM是将应用程序域模型对象映射到关系数据库表的编程技术。Hibernate是一个基于Java的ORM工具,它提供了一个框架,用于将应用程序域对象映射到关系数据库表。
Hibernate提供了Java Persistence API的参考实现,使其成为具有松散耦合优势的ORM工具的绝佳选择。
3 什么是Spring Data JPA
Spring Data是Spring Framework的一部分。Spring Data存储库抽象的目标是显著减少为各种持久性存储实现数据访问层所需的代码量。
Spring Data JPA不是JPA提供者。它是一个库/框架,它在我们的JPA提供程序(如Hibernate)的顶部添加了一个额外的抽象层。
4 Hibernate和Spring Data JPA的关系
Hibernate是一个JPA实现,而Spring Data JPA是一个JPA数据访问抽象。Spring Data提供了GenericDao自定义实现的解决方案,它还可以通过方法名称约定代表您生成JPA查询。
Spring Data JPA不是一个实现或JPA提供者,它只是一个抽象,用于显著减少为各种持久性存储实现数据访问层所需的代码量。Spring Data JPA始终需要JPA提供程序,如Hibernate。
5 Spring data jpa概述
JPA Spring Data:致力于减少数据访问层(DAO)的开发量,开发者唯一要做的,就只是声明持久层的接口,其他都交给Spring Data JPA来完成。
框架怎么可能代替开发者实现业务逻辑呢?比如:当有一个 UserDao.findUserById() 这样一个方法声明,大致应该能判断出这是根据给定条件的 ID 查询出满足条件的 User 对象。Spring Data JPA 做的便是规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。
5 Spring data JPA使用
5.1 jar包引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
5.2 Spring data jpa配置
@Configuration
// 借助spring data实现自动化的jpa repository,只需编写接口无需编写实现类
// 相当于xml配置的<jpa:repositories base-package="com.example.repository" />
// repositoryImplementationPostfix默认就是Impl
// entityManagerFactoryRef默认就是entityManagerFactory
// transactionManagerRef默认就是transactionManager
@EnableJpaRepositories(basePackages = {"com.wtj.repository"},
repositoryImplementationPostfix = "Impl",
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager")
// 启用事务管理器
@EnableTransactionManagement
//审计功能 用来自动填充@CreateDate等
@EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider")
public class SpringDataJpaConfig {
@Bean
public DateTimeProvider dateTimeProvider(DateTimeService dateTimeService) {
return dateTimeService::getNow;
}
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
// 设置数据库类型(可使用org.springframework.orm.jpa.vendor包下的Database枚举类)
jpaVendorAdapter.setDatabase(Database.MYSQL);
// 设置打印sql语句
jpaVendorAdapter.setShowSql(true);
// 设置不生成ddl语句
jpaVendorAdapter.setGenerateDdl(false);
// 设置hibernate方言
jpaVendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5Dialect");
return jpaVendorAdapter;
}
// 配置实体管理器工厂
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
// 注入数据源
emfb.setDataSource(dataSource);
// 注入jpa厂商适配器
emfb.setJpaVendorAdapter(jpaVendorAdapter);
// 设置扫描基本包
emfb.setPackagesToScan("com.wtj.entity");
return emfb;
}
// 配置jpa事务管理器
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
// 配置实体管理器工厂
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
}
启用web支持还需要在Spring MVC配置类上添加@EnableSpringDataWebSupport
注解
@Configuration
@ComponentScan(basePackages = {"com.wtj.controller"})
@EnableWebMvc // 启用spring mvc
@EnableSpringDataWebSupport // 启用springmvc对spring data的支持
public class WebMvcConfig extends WebMvcConfigurerAdapter {
}
配置文件
server:
port: 20000
servlet:
context-path: /
spring:
datasource:
url: jdbc:mysql://localhost:3306/mytest1?useUnicode=true&serverTimezone=UTC&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
username: root
password: root
jpa:
database: MySQL
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
hibernate:
ddl-auto: update
ddl-auto 解释:
- create : 每次运行程序时,都会重新创建表,故而数据会丢失。
- create-drop:每次运行程序时会先创建表结构,然后待程序结束时清空表。
- upadte:每次运行程序,没有表时会创建表,如果对象发生改变会更新表结构,原有数据不会清空,只会更新(推荐使用)。
- validate:运行程序会校验数据与数据库的字段类型是否相同,字段不同会报错。
- none: 禁用DDL处理。
5.3 简单的Spring dat jpa例子
例子简单就不写service层了,直接在controller中调用。
创建实体类
@Data
@Entity
@Table(name = "account")
@ToString
public class Account {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "username", unique = true, nullable = false, length = 64)
private String username;
@Column(name = "password", nullable = false, length = 64)
private String password;
@Column(name = "email", length = 64)
private String email;
}
主键采用UUID策略
@GenericGenerator
是Hibernate提供的主键生成策略注解,注意下面的@GeneratedValue
(JPA注解)使用generator = "idGenerator"引用了上面的name = "idGenerator"主键生成策略。
JPA自带的几种主键生成策略
TABLE: 使用一个特定的数据库表格来保存主键。
SEQUENCE: 根据底层数据库的序列来生成主键,条件是数据库支持序列。这个值要与generator一起使用,generator 指定生成主键使用的生成器(可能是orcale中自己编写的序列)。
IDENTITY: 主键由数据库自动生成(主要是支持自动增长的数据库,如mysql)。
AUTO: 主键由程序控制,也是GenerationType的默认值。
dao层
@Repository
public interface AccountRepository extends JpaRepository<Account,String> {
}
controller层
@RestController
@RequestMapping(value = "/role")
public class AccountController {
@Autowired
private AccountRepository repository;
@PostMapping()
public Account save(@RequestBody Account account) {
return repository.save(account);
}
@DeleteMapping("/{id}")
public void delete(@PathVariable("id") String accountId) {
repository.deleteById(accountId);
}
@PutMapping("/{id}")
public Account update(@PathVariable("id") String accountId, @RequestBody Account account) {
account.setId(accountId);
return repository.saveAndFlush(account);
}
@GetMapping("/{id}")
public Account getAccountInfo(@PathVariable("id") String accountId) {
Optional<Account> optional = repository.findById(accountId);
return optional.orElseGet(Account::new);
}
}
最后在数据库中造几条假数据进行crud就可以了。
5.4 Repository接口
Repository 接口概述:
- Repository 接口是 Spring Data 的一个核心接口,它不提供任何方法,是一个空接口,即是一个标记接口。开发者需要在自己定义的接口中声明需要的方法 。
public interface Repository<T, ID extends Serializable> { }
- Spring Data可以让我们只定义接口,只要遵循Spring Data的规范,就无需写实现类。
- 若我们定义的接口继承了Repository,则该接口会被IOC容器识别为一个Repository Bean,纳入到IOC容器中,进而可以在该接口中定义满足一定规范的方法。实际上在IOC容器中放的是该接口的实现类,只不过spring帮我们实现了,实际上它是一个代理。
- 与继承 Repository 等价的一种方式,就是在持久层接口上使用 @RepositoryDefinition 注解,并为其指定 domainClass 和 idClass 属性,指定对象和主键类型。
Repository 的子接口:
基础的 Repository提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。它们的继承关系如下:
- Repository:仅仅是一个标识,表明任何继承它的均为仓库接口类.
- CrudRepository:继承Repository,实现了一组CRUD相关的方法.
- PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法.
- JpaRepository:继承PagingAndSortingRepository,实现一组JPA规范相关的方法.
- 自定义的 XxxxRepository 需要继承 JpaRepository,这样的XxxxRepository接口就具备了通用的数据访问控制层的能力.
- JpaSpecificationExecutor:不属于Repository体系,实现一组JPACriteria 查询相关的方法.
5.5 自定义条件查询语句
按照 Spring Data的规范,查询方法以find | read | get
开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写。
比如说现在要按照account的名称进行查询:
在AccountRepository接口中新增方法
@Repository
public interface AccountRepository extends JpaRepository<Account,String> {
Account findAccountByUsername(String username);
}
controller中新增方法:
@GetMapping("/name/{username}")
public Account getAccountByName(@PathVariable("username") String userName){
Account account = repository.findAccountByUsername(userName);
return account;
}
这样就可以根据名称进行查询了,当然你也可以使用这种方法进行复杂查询,spring data jpa中支持的关键字如下:
5.6 自定义sql语句
有些时候spring data jpa提供的查询条件满足不了业务需求的时候,可以使用自定义的sql来进行查询。
想要使用自定义sql需要使用@Query
注解,@Query注解使用起来很简单,默认的属性是value,就是当前写的SQL语句,有时会用到nativeQuery属性,这个属性是用来标记当前的SQL是本地SQL,还是符合JPA语法规范的SQL。这里需要解释一下本地SQL和JPA语法规范的SQL区别。
- 本地SQL,是根据实际使用的数据库类型写的SQL,这种SQL中使用到的一些语法格式不能被JPA解析以及可能不兼容其他数据库,这种SQL称为本地SQL,此时需要将nativeQuery属性设置为true,否则会报错。
- JPA语法规范的SQL,往往这种SQL本身是不适用于任何数据库的,需要JPA将这种SQL转换成真正当前数据库所需要的SQL语法格式。
JPA很好的一个特性就是用JPA语法规范写的SQL,会根据当前系统使用的数据库类型改变生成的SQL语法,兼容数据库类型的切换,如之前使用的是MySQL,现在换成Oracle,由于不同类型的数据库,SQL语法会有区别,如果使用的是mybatis,就需要手动去改SQL兼容Oracle,而JPA就不用啦,无缝对接。
很大的时候使用JPA感觉都是为了兼容后期可能会有数据库切换的问题,所以在使用JPA的时候,不要去使用本地SQL,这就违背了使用JPA的初衷,让nativeQuery属性保持默认值就可以啦!
AccountRepository中新增方法
@Query("select a from Account a where a.username = :username")
Account findAccountByName(@Param("username")String name);
@Query("select a from Account a where a.email = ?1")
Account findAccountByEmail(String email);
@Query(value = "select * from account where username = ?1",nativeQuery = true)
Account getAccount(String username);
- 在SQL上使用占位符的两种方式,第一种是使用":“后加变量的名称,第二种是使用”?“后加方法参数的位置。如果使用”:“的话,需要使用@Param注解来指定变量名;如果使用”?"就需要注意参数的位置。
- 使用JPA语句中SQL语句中直接用实体类代表表名,因为在实体类中使用了@Table注解,将该实体类和表进行了关联。
controller中新增方法(方法名还有url起的都比较随意。。。。。)
@GetMapping("/sql/{username}")
public Account findAccountByName(@PathVariable("username") String userName){
Account account = repository.findAccountByName(userName);
return account;
}
@GetMapping("/email/{email}")
public Account findAccountByEmail(@PathVariable("email") String email){
Account account = repository.findAccountByEmail(email);
return account;
}
@GetMapping("/username/{username}")
public Account getAccount(@PathVariable("username") String username){
Account account = repository.getAccount(username);
return account;
}
最后通过请求就可以获得数据并且在控制台可以看到打印出的响应的sql。
当自定义sql涉及到删除,修改,插入的操作的时候需要加上@Modifying
注解。注明当前方法是修改操作。