Spring Data 、Spring Data JPA 、Hibernate之间的关系及SpringDataJPA简单使用
该博客内容多为自己学习的记录
1.SpringData Jap,Hibernate,Jpa三者之间的关系
1.1 JPA和ORM框架(如Hibernate)之间的关系
Jpa是sun公司定义的一种ORM(Object relational mapping)规范, sun公司定义了一些编程的接口,由服务厂商来提供实现,常见ORM框架由Hibernate,TopLink等。
他们之间的关系:
JPA和Hibernate的关系如JDBC和JDBC驱动一样,JPA是规范,Hibernate除了做了ORM框架之外,也是一种JPA的实现。
1.2 JPA概述
JPA是Java Persistence API的简称,中文名为Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
JPA规范包含以下三个方面的内容:
- 一套API标准。在javax.persistence的包下面,用来操作实体对象,执行CRUD操作,框架在后台替代我们完成所有的事情,开发者从烦琐的JDBC和SQL代码中解脱出来。
- 面向对象的查询语言:Java Persistence QueryLanguage(JPQL)。这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合
- ORM(object/relational metadata)元数据的映射。JPA支持XML和JDK5.0注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中。
1.3 Spring Data JPA与JPA规范的关系
SpringData JPA是在JPA的基础提供了Repository层(dao层)的实现,可以自己选择使用什么ORM框架
好处:不同ORM框架之间切换需要编写的代码都是有差异的,使用SpringData JPA能使使用不同的ORM框架之间切换时不需要再更改代码。
可以理解为:Spring Data JPA基于JPA规范再次封装,
2. Springboot整合SpringDataJpa
2.1 导入依赖
<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>
2.2 相关配置
server:
port: 8080
servlet:
context-path: /
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
username: root
password: mysql123
driver-class-name: com.mysql.jdbc.Driver
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处理
3. 简单的REST CRUD实例
3.1 实体类
package com.example.springbootjpa.entity;
@Entity
@Table(name = "tb_user")
@Data
public class User {
@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"主键生成策略
一般简单的Demo示例中只会使用@GeneratedValue(strategy = GenerationType.IDENTITY)这种主键自增的策略,而实际数据库中表字段主键类型很少是int型的
JPA自带的几种主键生成策略
- TABLE: 使用一个特定的数据库表格来保存主键
- SEQUENCE: 根据底层数据库的序列来生成主键,条件是数据库支持序列。这个值要与generator一起使用,generator 指定生成主键使用的生成器(可能是orcale中自己编写的序列)
- IDENTITY: 主键由数据库自动生成(主要是支持自动增长的数据库,如mysql)
- AUTO: 主键由程序控制,也是GenerationType的默认值
3.2Dao层
package com.example.springbootjpa.repository;
public interface UserRepository extends JpaRepository<User, String> {
}
3.3 Controller层
在这个demo中省略Service层
package com.example.springbootjpa.controller;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserRepository userRepository;
@PostMapping()
public User saveUser(@RequestBody User user) {
return userRepository.save(user);
}
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable("id") String userId) {
userRepository.deleteById(userId);
}
@PutMapping("/{id}")
public User updateUser(@PathVariable("id") String userId, @RequestBody User user) {
user.setId(userId);
return userRepository.saveAndFlush(user);
}
@GetMapping("/{id}")
public User getUserInfo(@PathVariable("id") String userId) {
Optional<User> optional = userRepository.findById(userId);
return optional.orElseGet(User::new);
}
@GetMapping("/list")
public Page<User> pageQuery(@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
return userRepository.findAll(PageRequest.of(pageNum - 1, pageSize));
}
}
4. SpringData Jpa 使用详解
4.1 SpringData查询方法
步骤:
-
声明一个接口继承自Repository或者Repository的一个子接口,对于SpringData Jpa通常使用JpaRepository,如:
interface PersonRepository extends Repository<Person, Long> { … }
-
在接口给中声明查询方法
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }
4.2 定义Repository接口
4.2.1 选择性暴露CRUD方法
4.2.1.方法1
一种方法是定义一个BaseRepository接口继承Repository接口,并从CrudRepository中copy你想暴露的CRUD方法
//注意:MyBaseRepository上面加了@NoRepositoryBean注解
@NoRepositoryBean
public interface MyBaseRepository<T, ID> extends Repository<T, ID> {
Optional<T> findById(ID id);
<S extends T> S save(S entity);
}
public interface UserRepository2 extends MyBaseRepository<User, String> {
}
4.2.2 方法二:
另一种方法是使用@RepositoryDefinition
注解,并从CrudRepository中copy你想暴露的CRUD方法
@RepositoryDefinition(domainClass = User.class, idClass = String.class)
public interface UserRepository3 {
Optional<User> findById(String id);
User save(User user);
}
4.3 Repository方法的Null值的处理
从Spring Data2.0开始对于返回单个聚合实例的CRUD方法可以使用java8 Optional接口作为方法返回值来表明可能存在的缺省值,典型示例为CrudRepository的findById方法
另外Spring也提供了几个注解来处理Null值
@NonNullApi
: 在包级别使用来声明参数和返回值不能为Null@NonNull
: 在参数或返回值上使用,当它们不能为Null时(如果在包级别上使用了@NonNullApi注解则没有必要再使用@NonNull注解了)@Nullable
: 在参数或返回值上使用,当它们可以为Null时
5. 查询方法
5.1 查询创建Query Creation
Spring Data Jpa通过解析方法名创建查询,框架在进行方法名解析时,会先把方法名多余的前缀find…By, read…By, query…By, count…By以及get…By截取掉,然后对剩下部分进行解析,第一个By会被用作分隔符来指示实际查询条件的开始。 我们可以在实体属性
上定义条件,并将它们与And和Or连接起来,从而创建大量查询:
User findByUsername(String username);
List<User> findByUsernameIgnoreCase(String username);
List<User> findByUsernameLike(String username);
User findByUsernameAndPassword(String username, String password);
User findByEmail(String email);
List<User> findByEmailLike(String email);
List<User> findByIdIn(List<String> ids);
List<User> findByIdInOrderByUsername(List<String> ids);
void deleteByIdIn(List<String> ids);
Long countByUsernameLike(String username);
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 | findByFirstname,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 | ... findByFirstnameNotLike |
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 |
… where x.age in ?1 |
NotIn | findByAgeNotIn(Collection |
… 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) |
具体Spring Data Jpa对方法名的解析规则可参看官方文档
5.2 限制查询结果
Spring Data Jpa支持使用first
、top
以及Distinct
关键字来限制查询结果,如:
User findFirstByUsernameOrderByUsernameAsc(String username);
List<User> findTop10ByUsername(String username, Sort sort);
List<User> findTop10ByUsername(String username, Pageable pageable);
5.3 自定义查询Using@Query
@Query 注解的使用非常简单,只需在声明的方法上面标注该注解,同时提供一个 JPQL 查询语句即可
@Query("select u from User u where u.email = ?1")
User getByEmail(String eamil);
@Query("select u from User u where u.username = ?1 and u.password = ?2")
User getByUsernameAndPassword(String username, String password);
@Query("select u from User u where u.username like %?1%")
List<User> getByUsernameLike(String username);
5.4 使用命名参数 Using Named Parameters
默认情况下,Spring Data JPA使用基于位置的参数绑定,如前面所有示例中所述。 这使得查询方法在重构参数位置时容易出错。 要解决此问题,可以使用@Param
注解为方法参数指定具体名称并在查询中绑定名称,如以下示例所示:
@Query("select u from User u where u.id = :id")
User getById(@Param("id") String userId);
@Query("select u from User u where u.username = :username or u.email = :email")
User getByUsernameOrEmail(@Param("username") String username, @Param("email") String email);
5.5 Using SpEL Expressions
从Spring Data JPA release 1.4开始,Spring Data JPA支持名为entityName的变量。 它的用法是select x from #{#entityName} x
。 entityName的解析方式如下:如果实体类在@Entity注解上设置了name属性,则使用它。 否则,使用实体类的简单类名。为避免在@Query注解使用实际的实体类名,就可以使用#{#entityName}
进行代替。如以上示例中,@Query注解的查询字符串里的User都可替换为#{#entityName}
@Query("select u from #{#entityName} u where u.email = ?1")
User getByEmail(String eamil);
5.6 原生查询 Native Queries
@Query注解还支持通过将nativeQuery
标志设置为true来执行原生查询,同样支持基于位置的参数绑定及命名参数,如:
@Query(value = "select * from tb_user u where u.email = ?1", nativeQuery = true)
User queryByEmail(String email);
@Query(value = "select * from tb_user u where u.email = :email", nativeQuery = true)
User queryByEmail(@Param("email") String email);
注意:Spring Data Jpa目前不支持对原生查询进行动态排序,但可以通过自己指定计数查询
countQuery
来使用原生查询进行分页、排序,如:
@Query(value = "select * from tb_user u where u.username like %?1%",
countQuery = "select count(1) from tb_user u where u.username = %?1%",
nativeQuery = true)
Page<User> queryByUsernameLike(String username, Pageable pageable);
原生查询就是可以直接在数据库中执行的sql语句,如果不加nativeQuery=ture,则sql语句中对应的可能不是数据库表中的字段名,而是对应的实体名的字段名。
5.7 分页查询与排序
Spring Data Jpa可以在方法参数中直接传入Pageable
或Sort
来完成动态分页或排序,通常Pageable或Sort会是方法的最后一个参数,如:
@Query("select u from User u where u.username like %?1%")
Page<User> findByUsernameLike(String username, Pageable pageable);
@Query("select u from User u where u.username like %?1%")
List<User> findByUsernameAndSort(String username, Sort sort);
那调用repository方法时传入什么参数呢?
对于Pageable参数,在Spring Data 2.0之前我们可以new一个org.springframework.data.domain.PageRequest
对象,现在这些构造方法已经废弃,取而代之Spring推荐我们使用PageRequest的of方法
new PageRequest(0, 5);
new PageRequest(0, 5, Sort.Direction.ASC, "username");
new PageRequest(0, 5, new Sort(Sort.Direction.ASC, "username"));
PageRequest.of(0, 5);
PageRequest.of(0, 5, Sort.Direction.ASC, "username");
PageRequest.of(0, 5, Sort.by(Sort.Direction.ASC, "username"));
注意:*Spring Data PageRequest的page参数是从0开始的 zero-based page index*
对于Sort参数,同样可以new一个org.springframework.data.domain.Sort
,但推荐使用Sort.by方法
6 .自定义修改,删除Modifying Queries
单独使用@Query注解只是查询,如涉及到修改、删除则需要再加上@Modifying
注解,如:
@Transactional()
@Modifying
@Query("update User u set u.password = ?2 where u.username = ?1")
int updatePasswordByUsername(String username, String password);
@Transactional()
@Modifying
@Query("delete from User where username = ?1")
void deleteByUsername(String username);
注意:Modifying queries can only use void or int/Integer as return type!
7. 多表查询
级联查询,结合mybatis重新复习
呆更新