JPA的初入了解
JPA的简介
JPA的由来:
ORM框架能够将Java对象映射到关系映射型数据库中,能够直接持久化复杂的Java对象。
目前流行的ORM框架有:MyBatis、Hibernate、TopLink、JDO等。
JPA是由Sun官方提出的持久化规范,简化了SSM框架和现有的JavaEE、JavaSE应用开发工作,通过整合ORM框架,来提高统一的API接口。
JPA是什么(作用于哪里?)
JAP全称(Java Persitence API)是Sun官方提出的持久化规范,具有易于使用、伸缩性强等优点,还为Java开发人员提供了一种对象/关系映射工具来管理Java应用中的关系数据。
JPA收到了极大的支持和赞扬,其中包括了Spring与EJB3.0的开发团队。
JPA就是一套规范,不是一套产品,有点像Hibernate、TopLInk、JDO等等他们是一套产品,如果说这些产品实现了这个JPA规范,那么我们就可以称他们为JPA的实现产品。
Spring Data JPA的简介
Spring Data 是Spring的一个子项目,简化数据库访问,包括NoSQL非关系型数据库,另外还包括对关系数据库的访问支持,使用Spring Data的话会让我们更加的简单与轻松的来操作或数据访问的技术也变得更加方便。
Spring Data JPA是Spring基于ORM框架、JPA规范的基础封装的一套JPA应用框架,可以让我们开发人员使用简易的代码就能够实现数据库的数据访问和操作,大大的提高开发中的效率,Spring Data JPA其实就是Spring基于Hibernate之上构建的,方便在Spring Boot项目中使用。
Spring Data JPA让我们解脱了DAO层(数据访问层)的操作,基本上所有的CRUD(增、删、改、查)都可以依赖它实现。
快速上手
数据库脚本:
CREATE TABLE `sys_user` (
`usr_id` bigint(20) NOT NULL AUTO_INCREMENT,
`usr_name` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
`usr_password` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
`usr_role_id` bigint(20) DEFAULT NULL,
`usr_flag` int(11) DEFAULT NULL,
PRIMARY KEY (`usr_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
创建项目:
打开IntelliJ IDEA,选择Create New Project
创建一个Spring Initializr项目
点击Next下一步
点击Next下一步
点击Finish完成
项目pom.xml文件如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
注意:
需要将MySQL驱动包的版本能降低为5.x版本(我目前使用的是5.1.47版本),否则在IDEA中运行会报错。
application.properties添加配置信息:
#配置tomcat的端口号默认是8080
server.port=8080
#配置数据源相关新
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/crm?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
#配置JPA(Hibernate)相关信息
#spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
hibernate.换部门ddl.auto参数作用主要用于:自动创建、更新、验证数据库表结构,有四个值
- create:每次加载Hibernate时都会删除上一次生成的表,然后根据model类然后在重新生成新表,这就是导致数据库数据丢失的一个重要原因。
- create-drop:每次加载Hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
- update:最常用的属性,第一次加载Hibernate时根据model类会自动建立表的结构(但前提是先创建好数据库),以后加载Hibernate根据model类自动更新表结构,即使表变结构变了,但是表中的行依然存在,不会删除以前的行,一般在应用第一次运行起来才会建立。
- validate:每次加载Hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
还有其他的参数如:
hibernate.dialect:主要是指数据库方言(数据库类型)
show-sql:是否在日志中打印出自动生成的SQL,方便调试的时候查看
format_sql:是否格式化SQL
编写实体类代码如下:
package com.aiweiyi.qingjing.demo.pojo;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
@Data//get set方法
@Entity//标注对应
@Table(name = "sys_user")//对应数据库表名
public class User implements Serializable {
@Id//id
@GeneratedValue(strategy = GenerationType.IDENTITY)//主键的一些类型,如自增等功能
@Column(name = "usr_id")//对应的列名
private Long usrId;
@Column(name = "usr_name")
private String usrName;
@Column(name = "usr_password")
private String usrPassword;
@Column(name = "usr_role_id")
private Long usrRoleId;
@Column(name = "usr_flag")
private Integer usrFlag;
}
JPA注解说明如下:
- @Entity(name=“EntitiyName”)必须,用来标注一个数据库表对应的实体,数据库中创建的表名默认和类名一致,其中name为可选,对应数据库中的一个表,使用此注解标记POJO是一个JPA实体。
- @Table(name="",catalog="",schema="")可选,用来标注一个数据库标对应的实体,数据库中创建的表名默认和类名一致,与@Entity配合使用,只能标注在实体的class定义处,表示实体对应的数据库表的信息。
- @GeneratedValue(strategy=GenerationType,generator="")可选,strategy表示主键策略,有AUTO、IDENTITY、SEQUENCE和TABLE,分别是:ID自增杜娜红序列产生主键、表产生主键,generator表示主键生成器的名称。
- @Column(name=“usr_id”,nullable=false,length=16)可选,name:表示与数据库中的字段相互对应上,默认情形属性名称一致,nullable:表示该字段可以为空,默认为true,unique:表示该字段唯一表示,默认为false,length:表示字段的长度,仅对String(字符串字符有效)。
- @Transient可选,表示该属性并非一个到数据表的字段的映射,ORM框架将忽略该属性。
- @Enumerated可选,使用枚举的时候,我们希望数据库中存储的是枚举对应的String类型,而不是枚举的索引值,需要在属性上面添加@Enumerated(EnumType.STRING)注解。
编写Repository
public interface UserRepository extends JpaRepository<User,Long>, JpaSpecificationExecutor {
}
查看类图
新增语句
编写测试:
创建好之后UserRepository 通过结构注入到对应的类中,启动Spring Boot,会自动根据注解内容创建实现类并注入到目标类中.
@Slf4j//日志对象
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Resource
private UserRepository userRepository;
@Test
public void userSave() {
User user = new User();
user.setUserName("小C");
user.setUsrPassword("1234444");
user.setUsrFlag(1);
user.setUsrRoleId(null);
user = userRepository.save(user);
log.info("新增:" + user);
Assert.assertNotNull(user);
Assert.assertArrayEquals();
System.out.println(user);
}
}
执行结果:
预生成方法:
构建的Repository继承了JpaRepository,JpaRepository接口源码如下:
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAllById(Iterable<ID> var1);
<S extends T> List<S> saveAll(Iterable<S> var1);
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}
预生成方法就是我们在上面看到的方法,因为继承JpaRepository接口,而拥有父接口的这些内容,所有父接口拥有方法都可以直接调用,根据方法名也可以看出它的含义。
自定义查询:
Spring Data JPA 还可以根据接口方法名来实现数据库操作,主要的语法是findBy、readBy、queryBy、countBy、get**By后几面跟属性名称,利用这个功能仅需要在定义的Repository中添加对应的方法即可,使用时Spring Boot会自动帮我实现。
根据用户名查询用户:
public List<User>findUserByName(String usrName);//根据用户名查询用户:
加上一些关键字如:And、Or
public List<User> findByUserNameOrUsrPassword(String usrName,String usrPassword);//Or关键字
public List<User> findByUserNameAndUsrPassword(String usrName,String usrPassword);//And关键字
修改、删除、统计也是类似语法。
public List<User> findByUsrName(String usrName);//查询用户名的记录数
模糊查询的语法:
public List<User> findByUserNameLike(String usrName);//模糊查询的用户名
编写测试模糊查询:
@Test
public void userQueryLikeName() {
List<User> user = userRepository.findByUserNameLike("%青%");
user.forEach(u -> System.out.println("模糊查询:"+u));
}
执行结果:
复杂操作:
使用Spring Data大部分的查询都可以根据方式和基本操作,常用的添、删、改、查需求Spring Data JPA已经实现,但是对于复杂的数据库场景,动态生成方法不能满足,对此Spring Data JPA提供其他的解决方案。
自定义QL查询
使用Spring Data大部分的查询都可以根据方法名定义的方式来实现,但是由于某些原因必须使用自定义QL来查询,Spring Data有可以完美支持。
在Repository的查询发放上面使用@Query注解,在注解内写HQL来查询内容。
@Query("select u from User u where u.usrId=?1")通过实体类和属性进行查询,不能写表名和属性名
public List<User>findByUsrId(Long usrId);
还可以使用原生SQL更习惯,它也是支持的,需要在添加一个nativeQuery参数=true,这时写的是SQL,面向数据库表和字段名。
@Query(value="select * from sys_user where usr_id=?1",nativeQuery=true)//进行原生SQL的方式进行查询,(普通查询的方式)
public List<User> findByUsrIdTo(Long usrId);
执行效果:他们俩种效果都是一样的
还可以加入@Param来迟滞命名参数,提高代码可读性和维护性。
@Query(value = "select u from User u where u.usrId=:usr_id")
public List<User>findByUsrId(@Param(value = "usr_id") Long usrId);
如果涉及到了添、删、改、查需要加上@Modifying,也可以根据需要添加@Transactional对事务的支持、操作超时设置等
修改语句:
@Transactional(timeout = 10)
@Modifying
@Query("update User u set u.usrName=?1 where u.usrId=?2")
public int modifyNameById(String usrName,Long usrId);
删除语句:
删除方式1:
@Transactional(timeout = 10)
@Modifying
@Query("delete from User u where u.usrId=?1")
public int DeleteUserById(@Param(value = "usr_id")Long usrId);
删除方式2:
@Transactional(timeout = 10)
@Modifying
@Query("delete from User u where u.usrId=:usr_id")
public int DeleteUserById(@Param(value = "usr_id")Long usrId);
命名QL查询
除了使用@Query注解外,还可以预定先定义好一些查询,并未其命名,然后在Repository中添加相同命名的方法。
定义命名的Query:
@Data//get set方法
@Entity//标注对应
@Table(name = "sys_user")//对应数据库表名
@NamedQueries(@NamedQuery(name="User.findUserByName",
query="select u from User u where u.usrName=?1"))
public class User implements Serializable {
}
通过@NamedQueries注解可以定义多个命名Query,@NamedQuery的name属性定义了Query的名称,注意加上Entity名称,作为前缀,query属性定义查询语句。
UserRepository中定义的方法:
public interface UserRepository extends JpaRepository<User,Long>, JpaSpecificationExecutor {
//此方法对应User实体中的命名查询
//如果希望根据接口方法名来实现数据库操作,则应命名为:findUserByUsrname
public List<User>findUserByName(String usrName);
}
分页查询:
Spring Data JPA已经帮我妈内置了分页功能,在查询的方法中,需要传入Pageable,当查询有多个参数的时候Pageable建议作为最后一个参数传入。
@Query("select u from User u where u.usrRoleId=?1")
public Page<User> findPageByUsrFlag( Long roleId, Pageable pageable);
Pageable是Spring封装的分页的实现类,使用的时候需要传入页数、每页条数、排序规则,Page是Spring封装的分页对象,封装了总页数、分页数据等,返回独享除使用Page外,还可以使用Slice作为返回值。
Page与Slice的区别:
- Page接口继承自Slice接口,而Slice继承自Iterable接口。
- Page接口扩展了Slice接口,添加了获取总页数、元素总数量的方法,若返回Page接口时,必须执行两条SQL,一条复杂查询分页数据,另一条负责统计数据数量.
- 返回Slice结果时是,查询的SQL只会有查询分页数据这一条,不统计数据数量。
- 用途不一样:Slice不需要知道总页数、总数据量,只需要知道是否有下一页、上一页,是否是首页、尾页等,可用于前后端的数据分页。
分页代码:
@Test
public void queryPage(){
int page=0,size=2;
Sort sort =new Sort(Sort.Direction.DESC,"usrId");
Pageable pageable = PageRequest.of(page,size,sort);
Page<User> userPage=userRepository.findPageByUsrFlag(1L,pageable);
System.out.println("总记录数:"+userPage.getTotalElements());
System.out.println("总页数:"+userPage.getTotalPages());
System.out.println("当前页数:"+userPage.getNumber()+1);//从0开始所以加1,即第一页开始
System.out.println("每页记录数:"+userPage.getSize());
System.out.println("当前页记录数:"+userPage.getNumberOfElements());
userPage.forEach(user -> System.out.println(user));
// for (User u:userPage.getContent()
// ) {
// System.out.println("userId:"+u.getUsrId());
// }
//分页查询
}
执行效果:
复杂分页查询:
我们可以通过AND或者OR等连接词来不断拼接属性来构建多条件查询,但如果参数大于六个小时,方法名会变的非常的长,并且还不能解决动态多条件查询的场景,这时我们需要使用另外一个利器JpaSpecificationExecutor
JpaSpecificationExecutor是JPA2.0提供的Criteria API的使用封装,可以用于动态生成Query来满足我们业务中的各种复杂场景,Spring Data JPA为我们提供了JpaSpecificationExecutor接口,只要简单实现toPredicate 方法就可以实现复杂的查询,接下来我们看一下JpaSpecificationExecutor的源码:
public interface JpaSpecificationExecutor<T> {
//根据Specification条件查询耽搁对象,如果条件能查出来多个会报错
Optional<T> findOne(@Nullable Specification<T> var1);
//根据Specification条件查询List结果
List<T> findAll(@Nullable Specification<T> var1);
//根据Specification条件,分页查询
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
//根据Specification条件,带排序的查询结果
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
//根据Specification条件,查询数量
long count(@Nullable Specification<T> var1);
}
JpaSpecificationExecutor的源码很简单,根据Specification的查询条件返回List、Page或count数据。
@Nullable
Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
在使用toPredicate方法构建复杂查询的概念:
- Rootroot,代表可以查询和操作的实体对象的根,通过get(“属性名”)来获取对应的值。
- CriteriaQuery query,代表一个specific的顶层查询对象,它包含着查询的各个部分,比如我们的:form、select、by、order by等。
- CriteriaQuery cb,来构建CriteriaQuery的构造器对象,其实就相当于条件或条件分组,并以Predicate的形式返回。
复杂分页的代码:
service 层
public interface UserService {
public Page<User> findByMap(Map map, Pageable pageable);
}
serviceImpl层
@Service("userService")
public class UserServiceImpl implements UserService {
@Resource
private UserRepository userRepository;
@Override
public Page<User> findByMap(Map param, Pageable pageable) {
return userRepository.findAll(new Specification<User>(){
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb){
List<Predicate> predicates =new ArrayList<>();
if(param.get("usrName")!=null){
predicates.add(cb.like(root.get("usrName"),"%"+param.get("usrName")+"%"));
}
if(param.get("usrRoleId")!=null){
predicates.add(cb.equal(root.get("usrRoleId"),param.get("usrRoleId")));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
},pageable);
}
}
测试代码:
@Test
public void complexPage(){
int page=0,size=2;
Sort sort =new Sort(Sort.Direction.DESC,"usrId");
Pageable pageable = PageRequest.of(page,size,sort);
Map param=new HashMap();
param.put("usrRoleId",2L);
Page<User> userPage=userService.findByMap(param,pageable);
System.out.println("总记录数:"+userPage.getTotalElements());
System.out.println("总页数:"+userPage.getTotalPages());
System.out.println("当前页数:"+userPage.getNumber()+1);//从0开始所以加1,即第一页开始
System.out.println("每页记录数:"+userPage.getSize());
System.out.println("当前页记录数:"+userPage.getNumberOfElements());
userPage.forEach(user -> System.out.println(user));
// for (User u:userPage.getContent()
// ) {
// System.out.println("userId:"+u.getUsrId());
// }
//复杂分页
}
执行结果:
总结一下:使用了Spring Data JPA之后简化一些繁琐的DAO层(数据访问层)的代码,跟Hibernate一样,大部分一些添、删、改、查的方法以及SQL语句都封装好了,我们只需要调用即可。
希望能够帮助我的真果粒们!
谢谢大家的支持!