文章目录
1. 简介
在Spring Data套装基础之Repositories中我们介绍了Spring Data最核心和公共的部分,这里我们简单介绍一下Spring Data JPA。
Spring Data JPA主要是针对关系数据库的,Spring Data Repository的东西都能使用,在查询方面肯定更加强大。
2. JPA 与 Spring Data JPA
JPA(Java Persistence API)Java持久化API,JDK1.5开始支持Java持久化规范(JSR 338),即javax.persistence包相关接口。
JPA主要是为了规范ORM(对象关系映射技术,如Hibernate、TopLink、JDO等),统一抽象层,面向接口编程,这样更容易切换、维护、重构。
JPA主要包括:
- ORM(Object Relational Metadata)元数据的映射
- 实体对象来执行CRUD操作相关API
- JPQL(Java Persistence Query Language),面向对象查询语言,避免程序的SQL语句紧密耦合
JPA是ORM规范,Hibernate实现了JPA,Spring Data JPA在JPA之上添加另一层抽象,通过Repository简化了数据持久化的工作量。
3. Spring Data JPA 与 MyBatis
如果业务逻辑比较简单Spring Data JPA可能要好一些,因为很多时候不用自己写SQL。
如果业务逻辑比较复杂,MyBatis可能要更好一些,可以专注于SQL,方便统一管理,有问题好定位。
4. 接口与实现类
Repository接口:
- Repository:只要名字按规范,接口就可以直接使用,不需要写SQL
- CrudRepository:继承了Repository,添加了增删改查相关功能
- PagingAndSortingRepository:继承了CrudRepository,添加了了排序分页相关
- JpaRepository:继承了PagingAndSortingRepository,添加了更多增删改查的功能
- QueryByExampleExecutor:更底层的接口,JpaRepository继承了QueryByExampleExecutor,一些方法接受Example参数
- Specification:JPA中Root、CriteriaQuery、CriteriaBuilder的封装,用于构建过滤条件
- JpaSpecificationExecutor:更加灵活的查询方式,主要是为了更好的实现一些自定义的查询
- QueryDslPredicateExecutor:基于ORM、SQL等构建的通用查询API
实现类:
- SimpleJpaRepository:Spring Data JPA在处理Repository、UserCrudRepository、PagingAndSortingRepository、JpaRepository等接口使用的就是这个类
- QueryDslJpaRepository(弃用了,使用QuerydslJpaPredicateExecutor)
- QuerydslJpaPredicateExecutor:QueryDslPredicateExecutor实现类
5. 主键
@GenericGenerator:Hibernate主键生成策略注解
@GeneratedValue:Java注解,可以通过generator属性引用其他生成策略
也可以通过GenerationType指定,如@GeneratedValue(strategy = GenerationType.IDENTITY)
- AUTO:主键由程序控制,也是GenerationType的默认值
- TABLE:使用一个特定的数据库表格来保存主键
- SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列
- IDENTITY:主键由数据库自动生成
6. @Query
@Query可以直接指定SQL语句,只需要将nativeQuery设置为true
@Query(value = "select * from user where username like ?1",nativeQuery = true)
List<User> queryByUsername(String username);
在SQL语句中使用?1,?2…?n这样的占位符和方法参数对应。
默认,@Query是JPQL:
@Query("select u from User u where u.username like %?1%")
List<User> getByUsernameLike(String username);
注意:在JPQL查询使用的是类名User而不是表名user
当然,还可以使用@param方式的参数
@Query("select u from User u where u.id = :id")
User getById(@Param("id") String userId);
还可以使用@Modifying执行更新操作:
@Transactional()
@Modifying
@Query("update User set username = ?1 where id = ?2")
int updateUsernameById(Integer id, String username);
排序与分页:
@Query("select u from User u where u.username like %?1%")
Page<User> findByUsernameLikePageable(String username, Pageable pageable);
@Query("select u from User where username like %?1%")
List<User> findByUsernameAndSort(String username, Sort sort);
7. JpaSpecificationExecutor
Spring data JPA不会扫描JpaSpecificationExecutor,想要直接使用,需要继承一个Repository接口。
JpaSpecificationExecutor接受Specification做为参数,方面我们自定义查询。
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.junit4.SpringRunner;
import vip.mycollege.mysql.jpa.entity.User;
import javax.annotation.Resource;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserJpaSpecificationExecutorTest {
@Resource
private UserJpaSpecificationExecutor userJpaSpecificationExecutor;
@Test
public void testJpaSpecificationExecutor1() {
Specification<User> specification = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Predicate predicate = criteriaBuilder.equal(root.get("username"), "tim");
return predicate;
}
};
List<User> list = userJpaSpecificationExecutor.findAll(specification);
for (User users : list) {
System.out.println(users);
}
}
@Test
public void combin() {
Specification<User> specification = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> list = new ArrayList<>();
list.add(criteriaBuilder.equal(root.get("username"), "tim"));
list.add(criteriaBuilder.equal(root.get("age"), 20));
Predicate[] arr = new Predicate[list.size()];
return criteriaBuilder.and(list.toArray(arr));
}
};
List<User> list = userJpaSpecificationExecutor.findAll(specification);
for (User users : list) {
System.out.println(users);
}
}
@Test
public void orAnd() {
Specification<User> specification = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Predicate namePredicate = criteriaBuilder.equal(root.get("username"), "tim");
Predicate agePredicate = criteriaBuilder.equal(root.get("age"), 20);
Predicate nameAndAgePredicate = criteriaBuilder.and(namePredicate, agePredicate);
Predicate idPredicate = criteriaBuilder.equal(root.get("id"), 1);
return criteriaBuilder.or(nameAndAgePredicate, idPredicate);
}
};
Sort sort = Sort.by(Sort.Direction.DESC, "id");
List<User> list = userJpaSpecificationExecutor.findAll(specification, sort);
for (User users : list) {
System.out.println(users);
}
}
@Test
public void gt() {
Specification<User> specification = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.gt(root.get("id").as(Integer.class), 2);
}
};
Sort sort = Sort.by(Sort.Direction.DESC, "id");
List<User> list = userJpaSpecificationExecutor.findAll(specification, sort);
for (User users : list) {
System.out.println(users);
}
}
@Test
public void lt() {
Specification<User> specification = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.lt(root.get("id").as(Integer.class), 2);
}
};
Sort sort = Sort.by(Sort.Direction.DESC, "id");
List<User> list = userJpaSpecificationExecutor.findAll(specification, sort);
for (User users : list) {
System.out.println(users);
}
}
@Test
public void between() {
Specification<User> specification = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.between(root.get("id").as(Integer.class), 2,10);
}
};
Sort sort = Sort.by(Sort.Direction.DESC, "id");
List<User> list = userJpaSpecificationExecutor.findAll(specification, sort);
for (User users : list) {
System.out.println(users);
}
}
}
8. 映射关系
8.1 @OneToMany
一对多的关系,例如,一个专业Speciality有多个User。
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.util.Set;
@Entity
@Table(name = "speciality")
public class Speciality {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
@OneToMany(targetEntity = User.class, cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
@JoinColumn(name = "speciality_id")
private Set<User> Users;
@Column(name = "name",length = 20)
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Set<User> getUsers() {
return Users;
}
public void setUsers(Set<User> users) {
Users = users;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Speciality{" +
"id=" + id +
", Users=" + Users +
", name='" + name + '\'' +
'}';
}
}
然后,我们查询Speciality的时候就可以把关联的User都查出来。
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import vip.mycollege.mysql.jpa.entity.Speciality;
import javax.annotation.Resource;
import java.util.Iterator;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpecialityCrudRepositoryTest {
@Resource
private SpecialityCrudRepository specialityCrudRepository;
@Test
public void findAll(){
Iterator<Speciality> iterator = specialityCrudRepository.findAll().iterator();
while (iterator.hasNext()){
Speciality speciality = iterator.next();
System.out.println(speciality);
}
}
}
8.2 其他
除了最常用的@OneToMany,还有@ManyToMany、@ManyToOne、@OneToOne
OneToOne、OneToMany和ManyToMany可以设置mappedBy
在属于拥有关系中,没有mappedBy属性的实体是拥有者
两个实体中都又@ManyToMany,如果都没有设置mappedBy,则默认将生成两个实体表和两个连接表
8.3 CascadeType
选项 | 说明 |
---|---|
CascadeType.ALL | 包含下面四种 |
CascadeType.MERGE | 级联合并,若A属性修改了,那么关联对象B保存时同时修改A对象 |
CascadeType.REMOVE | 级联删除,A对象删除,那么关联对象B中的对象也会删除 |
CascadeType.PERSIST | 级联新增,A对象保存时,关联对象B中的对象也会保存 |
CascadeType.REFRESH | 级联刷新,获取A对象里也同时,也重新获取最新的关联对象B |
9. hibernate表处理策略
spring.jpa.hibernate.ddl-auto=update
参数 | 说明 |
---|---|
none | 禁用DDL处理,默认值 |
create | 每次运行程序时,都会重新创建表,故而数据会丢失 |
upadte | 每次运行程序,没有表时会创建表,如果对象发生改变会更新表结构,原有数据不会清空,只会更新 |
validate | 运行程序会校验数据与数据库的字段类型是否相同,字段不同会报错 |
create-drop | 每次运行程序时会先创建表结构,然后待程序结束时清空表 |
10. 注解
注解 | 说明 |
---|---|
@Id | 指定表主键 |
@Lob | 将属性映射成数据库支持的大对象类型 |
@Table | 声明表名 |
@Basic | 指定非约束明确的各个字段 |
@Entity | 声明类为实体 |
@Column | 持久字段属性 |
@IdClass | 联合主键 |
@OrderBy | 排序 |
@Embedded | 指定类或它的值是一个可嵌入的类的实例的实体的属性 |
@OneToOne | 定义了连接表之间有一个一对一的关系 |
@Transient | 表示该属性并非一个到数据库表的字段的映射,JPA将忽略该属性 |
@EmbeddedId | 可嵌入式联合主键 |
@Enumerated | 映射enum枚举类型的字段 |
@AccessType | FIELD直接访问字段,PROPERTY通过getter、setter方法字段 |
@JoinColumn | 连接字段 |
@ManyToOne | 定义连接表之间多对一关系 |
@OneToMany | 定义连接表之间一对多关系 |
@ManyToMany | 定义连接表之间多对多关系 |
@NamedQuery | 在实体类上,声明JPQL配置方法 |
@JoinColumns | 里面有多个@JoinColumn |
@EntityGraph | 为了提高查询效率。解决N+1条SQL的问题 |
@ColumnResult | 字段返回值 |
@EntityResult | 实体返回值 |
@NamedQueries | 在实体类上,声明式JPQL配置方法 |
@GeneratedValue | 指定主键生成方式 |
@TableGenerator | 指定主键生成器 |
@NamedNativeQuery | 在实体类上,声明原始SQL配置 |
@NamedEntityGraph | 为了提高查询效率,解决N+1条SQL的问题 |
@UniqueConstraint | 指定的字段和用于主要或辅助表的唯一约束 |
@TemporalType.DATE | 映射只有日期的date |
@TemporalType.TIME | 映射只有时间的time |
@SequenceGenerator | 指定在@GeneratedValue注解中指定的属性的值。它创建了- -个序列 |
@SqlResultSetMapping | 字段属性映射 |
@SqlResultSetMappings | 多个@SqlResultSetMapping |
@TemporalType.TIMESTAMP | 映射为日期datetime |