JPA全称Java Persistence API,可以通过注解或者XML描述【对象-关系表】之间的映射关系,并将实体对象持久化到数据库中。JPA的出现主要是为了简化持久层开发以及整合ORM技术,结束Hibernate、TopLink、JDO等ORM框架各自为营的局面。
JAP为我们提供了 ORM映射元数据 , JPA的API , JPQL查询语言 等,但JPA仅仅是一种规范, 也就是说JPA仅仅定义了一些接口 ,而接口是需要实现才能工作的。所以底层需要某种实现,而Hibernate就是实现了JPA接口的ORM框架。
Hibernate是Java中的对象关系映射解决方案。对象关系映射或ORM是将应用程序域模型对象映射到关系数据库表的编程技术。Hibernate是一个基于Java的ORM工具,它提供了一个框架,用于将应用程序域对象映射到关系数据库表。
Hibernate提供了Java Persistence API的参考实现,使其成为具有松散耦合优势的ORM工具的绝佳选择。
Spring Data是Spring Framework的一部分。Spring Data存储库抽象的目标是显著减少为各种持久性存储实现数据访问层所需的代码量。
Spring Data JPA不是JPA提供者。它是一个库/框架,它在我们的JPA提供程序(如Hibernate)的顶部添加了一个额外的抽象层。
Hibernate是一个JPA实现,而Spring Data JPA是一个JPA数据访问抽象。Spring Data提供了GenericDao自定义实现的解决方案,它还可以通过方法名称约定代表您生成JPA查询。
Spring Data JPA不是一个实现或JPA提供者,它只是一个抽象,用于显著减少为各种持久性存储实现数据访问层所需的代码量。Spring Data JPA始终需要JPA提供程序,如Hibernate。
JPA Spring Data:致力于减少数据访问层(DAO)的开发量,开发者唯一要做的,就只是声明持久层的接口,其他都交给Spring Data JPA来完成。
框架怎么可能代替开发者实现业务逻辑呢?比如:当有一个 UserDao.findUserById() 这样一个方法声明,大致应该能判断出这是根据给定条件的 ID 查询出满足条件的 User 对象。Spring Data JPA 做的便是规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。
<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> 复制代码
@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 解释:
例子简单就不写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的默认值。
@Repository public interface AccountRepository extends JpaRepository<Account,String> { } 复制代码
@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就可以了。
public interface Repository<T, ID extends Serializable> { } 复制代码
Repository 的子接口:
基础的 Repository提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。它们的继承关系如下:
按照 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中支持的关键字如下:
有些时候spring data jpa提供的查询条件满足不了业务需求的时候,可以使用自定义的sql来进行查询。
想要使用自定义sql需要使用 @Query
注解,@Query注解使用起来很简单,默认的属性是value,就是当前写的SQL语句,有时会用到nativeQuery属性,这个属性是用来标记当前的SQL是本地SQL,还是符合JPA语法规范的SQL。这里需要解释一下本地SQL和JPA语法规范的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); 复制代码
@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
注解。注明当前方法是修改操作。