jpa它并不是一个框架,而是一组规范。我觉得对于任何一个开发人员来说,理解“规范”这个词应该不在话下。其中,Hibernate就实现了这个规范。
众所周知,Spring 是一个大家庭,一个技术的生态圈,其中整合的内容包罗万象。而 Spring Data xxx 系列就是 Spring 这个生态圈对数据持久化层的整合。
Spring Data是Spring用来解决数据访问的解决方案,它包含了大量关系型数据库以及NoSQL数据库的数据持久层访问解决方案。它包含Spring Data JPA、Spring Data MongoDB、Spring Data Neo4j、Spring Data GernFile、Spring Data Cassandra等。
目前现有的 Spring-data-jpa,Spring-data-template,Spring-data-mongodb,Spring-data-redis 等。当然,也包括最开始用的 mybatis-spring ,MyBatis 与 Spring 的整合。
于是,就出现了 Spring-data-jpa ,Spring 与 JPA 的整合。具体详情参看 Spring Data 官网介绍 。
正如上所说,Spring Data JPA 是 Spring 与 JPA 的整合。是基于 JPA 对数据库访问层的增强支持。旨在简化数据库访问层,减少工作量。
具体详情参看 Spring Data JPA 官网。
对于开发者来说,使用起来很简单。下面我们看看 Spring Data JPA 几个核心概念。
Repository:最顶层的接口,是一个空的接口,目的是为了统一所有Repository的类型,且能让组件扫描的时候自动识别;
CrudRepository :是Repository的子接口,提供CRUD的功能;
@NoRepositoryBean
public interface CrudRepository extends Repository {
S save(S entity);
Iterable saveAll(Iterable entities);
Optional findById(ID id);
boolean existsById(ID id);
Iterable findAll();
Iterable findAllById(Iterable ids);
long count();
void deleteById(ID id);
void delete(T entity);
void deleteAll(Iterable extends T> entities);
void deleteAll();
}
JpaRepository:是PagingAndSortingRepository的子接口,增加了一些实用的功能,比如:批量操作等;
@NoRepositoryBean
public interface JpaRepository extends PagingAndSortingRepository, QueryByExampleExecutor {
List findAll();
List findAll(Sort var1);
List findAllById(Iterable var1);
List saveAll(Iterable var1);
void flush();
S saveAndFlush(S var1);
void deleteInBatch(Iterable var1);
void deleteAllInBatch();
T getOne(ID var1);
List findAll(Example var1);
List findAll(Example var1, Sort var2);
}
PagingAndSortingRepository:是 CrudRepository 的子接口,添加分页和排序的功能;
@NoRepositoryBean
public interface PagingAndSortingRepository extends CrudRepository {
Iterable findAll(Sort sort);
Page findAll(Pageable pageable);
}
Specification:是Spring Data JPA提供的一个查询规范,要做复杂的查询,只需围绕这个规范来设置查询条件即可;
public interface Specification extends Serializable {
long serialVersionUID = 1L;
static Specification not(Specification spec) {
return Specifications.negated(spec);
}
static Specification where(Specification spec) {
return Specifications.where(spec);
}
default Specification and(Specification other) {
return Specifications.composed(this, other, CompositionType.AND);
}
default Specification or(Specification other) {
return Specifications.composed(this, other, CompositionType.OR);
}
@Nullable
Predicate toPredicate(Root var1, CriteriaQuery> var2, CriteriaBuilder var3);
}
JpaSpecificationExecutor:用来做负责查询的接口。
public interface JpaSpecificationExecutor {
Optional findOne(@Nullable Specification var1);
List findAll(@Nullable Specification var1);
Page findAll(@Nullable Specification var1, Pageable var2);
List findAll(@Nullable Specification var1, Sort var2);
long count(@Nullable Specification var1);
创建一个springboot项目,然后添加以下一些依赖:
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
org.projectlombok
lombok
1.14.4
#DB Configuration:
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/baijie?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
#JPA Configuration:
spring.jpa.database=MySQL
#日志显示SQL语句
spring.jpa.show-sql=true
#初始化数据库结构,
#spring.jpa.generate-dd和spring.jpa.hibernate.ddl-auto功能类似,选一即可!
#spring.jpa.generate-ddl=true
#自动更新
spring.jpa.hibernate.ddl-auto=update
其他配置明细:
spring.jpa.hibernate.ddl-auto
的四个属性的含义见下表:
属性值 | 作用 |
---|---|
create | 每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。 |
create-drop | 每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。 |
update | 最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据 model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。 |
validate | 每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。 |
【细节】SpringBoot启动时初始化数据库及spring.jpa.generate-dll与spring.jpa.hibernate.ddl-auto之间的困惑
解答参考链接:https://www.cnblogs.com/StarkBrothers/p/11804554.html
@Entity 表示这个类是一个实体类,接受JPA控制管理,对应数据库中的一个表
@Table 指定这个类对应数据库中的表名。如果这个类名和数据库表名符合驼峰及下划线规则,可以省略这个注解。
@Id 指定这个字段为表的主键
@GeneratedValue(strategy=GenerationType.IDENTITY) 指定主键的生成方式
@Column 注解针对一个字段,对应表中的一列。nullable = false表示数据库字段不能为空, unique = true表示数据库字段不能有重复值,length = 32表示数据库字段最大程度为32.
如图展示:
JPA提供的四种策略生成器,标准用法为:TABLE,SEQUENCE,IDENTITY,AUTO.
TABLE:使用一个特定的数据库表格来保存主键。
SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
IDENTITY:主键由数据库自动生成(主要是自动增长型)
AUTO:主键由程序控制。
@Entity
@Data
public class User {
// 主键
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 用户名
private String name;
// 密码
private String password;
}
public interface UserRepository extends JpaRepository{
}
@SpringBootTest
public class JpaTest {
@Autowired
private UserRepository userDaoInterface;
@Test
public void test1(){
List all = UserRepository.findAll();
for (User user : all) {
System.out.println(user.toString());
}
}
}
【温馨提示】在上述示例中,我们使用的是 JPA 的内置方法 findAll()
,另外也有很多别的方法,如下:
解决方案:手动导入对应的maven坐标,如下:
javax.xml.bind
jaxb-api
2.3.0
/**
* 保存
*/
@Test
public void saveUser(){
User user=new User();
user.setName("张飞");
user.setPassword("666");
dao.save(user);
}
/**
* 修改
*/
@Test
public void updateUser(){
Optional opt = dao.findById(62);
User user = opt.get();
System.out.println(user.toString());
user.setPassword("999999");
dao.save(user);
}
/**
* 删除
*/
@Test
public void deleteById(){
dao.deleteById(70);
}
/**
* 删除方式2
*/
@Test
public void deleteById(){
User user=new User();
user.setId(89l);
dao.delete(user);
}
【温馨提示】导入包时,小心别导入错了哦。比如Pageable接口的包如下:
import org.springframework.data.domain.Pageable;
Pageable接口是一个分页接口。PageRequest类是此接口中的一个实现类!
单元测试:
大家思考一个问题。为什么5.3.8中的方法findByName()明明是自己写的,为什么会成功执行?
答:因为方法名碰巧符合了spring Data Jpa方法命名规则!
顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询。
按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,关键字后面连接的就是实体类的属性名。要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
比如:
具体的关键字,使用方法和生产成SQL如下表所示:
Keyword | Sample | JPQL | ||
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) |
多个参数模糊查询: