一文熟练使用spring data jpa
Spirng Data Jpa
简介
什么是SpringData
Spring Data 项目的目的是为了简化构建基于 Spring 框架应用的数据访问技术,包括非关系数据库、Map-Reduce 框架(大数据里会讲的映射-规约)、云数据服务等等;另外也包含对关系数据库的访问支持。
Spring Data 旨在统一和简化对各类型持久化存储, 而不拘泥于是关系型数据库还是NoSQL 数据存储。
SpringData是一个用于简化数据库访问,并支持云服务的开源框架。
主要目标是使得数据库的访问变得方便快捷,并支持map-reduce框架和云计算机数据服务。
支持基于关系型数据库的数据服务,如Oracle RAC(RAC是real application clusters的缩写,译为“实时应用集群”, 是Oracle新版数据库中采用的一项新技术)等。
对于拥有海量数据的项目,可以用SpringData来简化项目的开发,就如Spring Framework对JDBC,ORM的支持一样,SpringData会让数据访问变得更加方便。
spring data 包含多个子项目:
- Commons - 提供共享的基础框架,适合各个子项目使用,支持跨数据库持久化
- Hadoop - 基于 Spring 的 Hadoop 作业配置和一个 POJO 编程模型的 MapReduce 作业
- Key-Value - 集成了 Redis 和 Riak ,提供多个常用场景下的简单封装
- Document - 集成文档数据库:CouchDB 和 MongoDB 并提供基本的配置映射和资料库支持
- Graph - 集成 Neo4j 提供强大的基于 POJO 的编程模型
- Graph Roo AddOn - Roo support for Neo4j
- JDBC Extensions - 支持 Oracle RAD、高级队列和高级数据类型
- JPA - 简化创建 JPA 数据访问层和跨存储的持久层功能
- Mapping - 基于 Grails 的提供对象映射框架,支持不同的数据库
- Examples - 示例程序、文档和图数据库
- Guidance - 高级文档
Spring Data项目旨在为大家提供一种通用的编码模式,统一我们的API
spring-data-jpa:整合springdata和jpa的意思
-
JPA诞生的缘由是为了整合第三方ORM框架,建立一种标准的方式,百度百科说是JDK为了实现ORM的天下归一,目前也是在按照这个方向发展,但是还没能完全实现。在ORM框架中,Hibernate是一支很大的部队,使用很广泛,也很方便,能力也很强,同时Hibernate也是和JPA整合的比较良好,我们可以认为JPA是标准,事实上也是,JPA几乎都是接口,实现都是Hibernate在做,宏观上面看,在JPA的统一之下Hibernate很良好的运行。
上面阐述了JPA和Hibernate的关系,那么Spring-data-jpa又是个什么东西呢?这地方需要稍微解释一下,我们做Java开发的都知道Spring的强大,到目前为止,企业级应用Spring几乎是无所不能,无所不在,已经是事实上的标准了,企业级应用不使用Spring的几乎没有,这样说没错吧。而Spring整合第三方框架的能力又很强,他要做的不仅仅是个最早的IOC容器这么简单一回事,现在Spring涉及的方面太广,主要是体现在和第三方工具的整合上。而在与第三方整合这方面,Spring做了持久化这一块的工作,于是就有了Spring-data-**这一系列包。包括,Spring-data-jpa,Spring-data-template,Spring-data-mongodb,Spring-data-redis还有一个不是spring做的mybatis-spring这些都是干的持久化工具干的事儿。 -
我们都知道,在使用持久化工具的时候,一般都有一个对象来操作数据库,在原生的Hibernate中叫做Session,在JPA中叫做EntityManager,在MyBatis中叫做SqlSession,通过这个对象来操作数据库。我们一般按照三层结构来看的话,Service层做业务逻辑处理,Dao层和数据库打交道,在Dao中,就存在着上面的对象。那么ORM框架本身提供的功能有什么呢?答案是基本的CRUD,所有的基础CRUD框架都提供,我们使用起来感觉很方便,很给力,业务逻辑层面的处理ORM是没有提供的,如果使用原生的框架,业务逻辑代码我们一般会自定义,会自己去写SQL语句,然后执行。在这个时候,Spring-data-jpa的威力就体现出来了,ORM提供的能力他都提供,ORM框架没有提供的业务逻辑功能Spring-data-jpa也提供,全方位的解决用户的需求。使用Spring-data-jpa进行开发的过程中,常用的功能,我们几乎不需要写一条jpql或者sql语句,复杂的功能,比如子查询啊这些,才需要自己写jpql或者sql语句。实现了进一步的简化Dao层的代码量!
简单例子
所需依赖
org.springframework.boot spring-boot-starter-data-jpa mysql mysql-connector-java org.springframework.boot spring-boot-starter
- 配置文件application.yml
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.175.100:3306/spring-data-jpa?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC username: root password: [email protected] jpa: database: mysql # 控制台打印sql show-sql: true# 控制@Entity注解的类生成数据库表 generate-ddl: true hibernate: ddl-auto: update
- 写一个entity实体类
import javax.persistence.*;import java.util.Date;//标记entity,会自动生成数据表@Entity@Table(name = "t_user")public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String username; private Integer age; @Column(name = "created_time") private Date createdTime; public Date getCreatedTime() { return createdTime; } public void setCreatedTime(Date createdTime) { this.createdTime = createdTime; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", age=" + age + ", createdTime=" + createdTime + '}'; }}
- 创建一个接口实现Repository
import com.nanmeishu.springdatajpa.jpademo.entity.User;import org.springframework.data.repository.Repository;import org.springframework.data.repository.RepositoryDefinition;/* * @RepositoryDefinition(domainClass = User.class,idClass = Integer.class)的效果 * 与extends Repository一致 */public interface UserRepository extends Repository { User getUserById(int id);}
解析Repository接口
- 通过查看Repository接口的源码发现,它是一个标记接口:
import org.springframework.stereotype.Indexed;@Indexedpublic interface Repository {}
- 这个标记接口的作用是把继承它的子接口,比如这里的UserRepository标记为Repository Bean,Spring就会为这个接口创建代理实现类,并且放入到IOC反转容器里,就像我们以前自己写的UserDao的实现类UserDaoImpl一样。
- 在持久层的接口中定义方法
User getUserById(int id);
在这个接口中定义的方法,要遵守一些规定:
- 查询的方法:find或get或read开头
- 查询条件:用关键字链接,涉及属性首写字母大写
举例:
- getByIdAndUsername(int id,String username);
- getById(int id);
- getByIdLessThanEqual(int id);
- getByGreaterThanEqual(int id);
- getByUsernameLike(String str);
- getByUsernameContaining(string str)
支持级联查询,关联对象和它的属性之间用_连接
@Entity@Table(name = "t_user")@Datapublic class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private Integer age; @Column(name = "created_time") private Date createdTime; private String username; //多对一 @ManyToOne private Address address;}
@Data@Entity@Table(name = "t_address")public class Address { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "address_id") private Integer addressId; @Column(name = "address_name") private String addressName;
/* * @RepositoryDefinition(domainClass = User.class,idClass = Integer.class)的效果 * 与extends Repository一致 */public interface UserRepository extends Repository { User getUserById(int id); List getUserByAddress_AddressNameContaining(String str);}
自定义sql
spring data jpa使用@Query来进行自定义sql,语法为jpsql
注:@Query操作的对象为实体类,而不是数据表!!!
- 没参数的@Query查询:实现查找User中age最大的哪个记录返回相应对象:
@Query("select u from User u where u.age=(select max(uu.age) from User uu)")User getMaxAge();
- 带参数的@query查询,使用占位符:
@Query("select u from User u where u.username=?1 and u.age=?2")User getUserByUA(String username,Integer age);
- 带参数的@query查询,使用具名参数:
@Query("select u from User u where u.username=:username and u.age=:age")User getUA(@Param("age") Integer age,@Param("username") String username);
- 带参数的@query查询,使用like的时候,是可以用%
@Query("select u from User u where u.username like %?1%")List listUserLike(String str);
- 使用@query执行原生的sql查询:
@Query(value = "select * from t_user",nativeQuery = true)List listUser();
- 在@Query注解的上面加上@Modifying,那么就可以执行update和delete的jpql语句了:
@Modifying@Query("update User u set u.username=?2 where u.id=?1")void updateUser(Integer id,String username);@Modifying@Query("delete from User u where u.id=?1")void deleteUser(Integer id);
调用更新/删除
import com.nanmeishu.springdatajpa.jpademo.repository.UserRepository;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;@Servicepublic class UserService { @Autowired UserRepository userRepository; @Transactional public void updateUser(int id,String username){ userRepository.updateUser(id,username); }}
- 注意:直接执行这个方法是不行的,必须在service层里调用它才可以,原因是Springdata执行update和delete语句需要可写事务支持的,我们事务是放在service层的!Springdata需要事务,那么为什么前面的查询可以直接执行呢?默认情况下Springdata也是有事务的,只不过这个事务是只读事务,不能修改和删除数据库里的记录。
- 另外还要注意一个问题,注解@Modifying是不能做insert语句的,jpql不支持!
Repository实现类的使用
Repository接口还有子接口和实现类
重点介绍开发中常用的JpaRepository
CrudRepository
这个接口下就已经帮我们定义好了,增删改查方法,直接调用就可以了:
import com.nanmeishu.springdatajpa.jpademo.entity.User;import org.springframework.data.repository.CrudRepository;public interface UserCurdRepository extends CrudRepository {}
批量新增
List users=new ArrayList();for (int i = 0; i < 30; i++) { User user=new User(); user.setUsername("user"+i); user.setAge(i); users.add(user);}userCurdRepository.saveAll(users);
PagingAndSortingRepository
这个接口下在上一个接口的基础上有增加了一些方法:
import com.nanmeishu.springdatajpa.jpademo.entity.User;import org.springframework.data.repository.PagingAndSortingRepository;public interface UserPageRepository extends PagingAndSortingRepository {}
-
分页
//页码 第一页为0int current=0;//每页显示多少记录int size=5;PageRequest pageRequest=PageRequest.of(current,size);Page all = userPageRepository.findAll(pageRequest);System.out.println("数据:"+all.getContent());System.out.println("当前页码,(此页码第一页为0):"+all.getNumber());System.out.println("总页码数:"+all.getTotalPages());System.out.println("当前页记录数:"+all.getNumberOfElements());System.out.println("总记录数:"+all.getTotalElements());
-
分页+排序
//页码 第一页为0int current=0;//每页显示多少记录int size=5;//可以排序多个字段List orders=new ArrayList();Sort.Order order1=new Sort.Order(Sort.Direction.DESC,"id");orders.add(order1);//排序//Sort sort = Sort.by(Sort.Direction.DESC, "id");Sort sort = Sort.by(orders);//分页+排序PageRequest pageRequest=PageRequest.of(current,size,sort);Page all = userPageRepository.findAll(pageRequest);System.out.println("数据:"+all.getContent());System.out.println("当前页码,(此页码第一页为0):"+all.getNumber());System.out.println("总页码数:"+all.getTotalPages());System.out.println("当前页记录数:"+all.getNumberOfElements());System.out.println("总记录数:"+all.getTotalElements());
JpaRepository(推荐使用)
JpaRepository这个接口在上面的接口的基础上,又增加了很多方法:
使用方法
import com.nanmeishu.springdatajpa.jpademo.entity.User;import org.springframework.data.jpa.repository.JpaRepository;public interface UserJpaRepository extends JpaRepository {}
新增
- 新增一条记录
User user=new User();user.setAge(12);user.setUsername("蓝莓鼠");user.setCreatedTime(new Date());//新增,不传入iduserJpaRepository.save(user);
- 批量新增
List users=new ArrayList();for (int i = 30; i < 60; i++) { User user=new User(); user.setUsername("南美鼠"+i); user.setAge(i); users.add(user);}userJpaRepository.saveAll(users);
更新
User user=new User();user.setAge(12);user.setUsername("蓝莓鼠");user.setCreatedTime(new Date());user.setId(1);//更新,注:此方法传入id为更新,不传id为新增userJpaRepository.saveAndFlush(user);
删除
- 通过id删除
Integer id=1;userJpaRepository.deleteById(id);
- 批量删除
List ids=new ArrayList();ids.add(1);ids.add(2);userJpaRepository.deleteAllByIdInBatch(ids);
- 删除所有数据
userJpaRepository.deleteAll();
查询
- 通过id查询一条数据
int id=1;Optional byId = userJpaRepository.findById(id);System.out.println(byId.get());
- 通过id集合查询数据列表
List ids=new ArrayList();ids.add(1);ids.add(2);ids.add(3);List allById = userJpaRepository.findAllById(ids);
- 查询所有数据集合
List all = userJpaRepository.findAll();
- 查询条件为(age=2)的数据集合
User user=new User();user.setAge(2);Example example = Example.of(user);List users = userJpaRepository.findAll(example);
- 查询条件为(age=2 and username=user1)的数据集合
User user=new User();user.setAge(2);user.setUsername("user1");Example example = Example.of(user);List users = userJpaRepository.findAll(example);
- 查询条件为(username like ‘%user%’ and age=2)的数据集合
User user=new User();user.setAge(2);user.setUsername("user");user.setId(1);ExampleMatcher matcher = ExampleMatcher.matching() //构建对象 .withMatcher("username",ExampleMatcher.GenericPropertyMatchers.contains())// id like %?% .withMatcher("age", ExampleMatcher.GenericPropertyMatchers.exact())//匹配 .withIgnorePaths("id");//忽略此字段Example example=Example.of(user,matcher);List all = userJpaRepository.findAll(example);
- 关联查询
只需要User类里添加对应注解
//多对一@ManyToOneprivate Address address;
关系如下
@OneToOne //一对一
@OneToMany //一对多
@ManyToOne //多对一
@ManyToMany//多对多
- 查询条件为(username like ‘%user%’ and age=2 and address表下address_id=2)的数据集合
User user=new User();user.setAge(2);user.setUsername("user");user.setId(1);Address address=new Address();address.setAddressId(2);user.setAddress(address);ExampleMatcher matcher = ExampleMatcher.matching() //构建对象 .withMatcher("username",ExampleMatcher.GenericPropertyMatchers.contains())// id like %?% .withMatcher("age", ExampleMatcher.GenericPropertyMatchers.exact())//匹配 .withMatcher("addressId", ExampleMatcher.GenericPropertyMatchers.exact())//匹配 .withIgnorePaths("id");//忽略此字段Example example=Example.of(user,matcher);List all = userJpaRepository.findAll(example);
- 分页查询
//页码 第一页为0int current=0;//每页显示多少记录int size=5;PageRequest pageRequest=PageRequest.of(current,size);Page all = userJpaRepository.findAll(pageRequest);System.out.println("数据:"+all.getContent());System.out.println("当前页码,(此页码第一页为0):"+all.getNumber());System.out.println("总页码数:"+all.getTotalPages());System.out.println("当前页记录数:"+all.getNumberOfElements());System.out.println("总记录数:"+all.getTotalElements());
- 分页+排序查询
//页码 第一页为0int current=0;//每页显示多少记录int size=5;//可以排序多个字段List orders=new ArrayList();Sort.Order order1=new Sort.Order(Sort.Direction.DESC,"id");orders.add(order1);//排序//Sort sort = Sort.by(Sort.Direction.DESC, "id");Sort sort = Sort.by(orders);//分页+排序PageRequest pageRequest=PageRequest.of(current,size,sort);Page all = userJpaRepository.findAll(pageRequest);System.out.println("数据:"+all.getContent());System.out.println("当前页码,(此页码第一页为0):"+all.getNumber());System.out.println("总页码数:"+all.getTotalPages());System.out.println("当前页记录数:"+all.getNumberOfElements());System.out.println("总记录数:"+all.getTotalElements());
- 分页+排序+条件查询
//页码 第一页为0int current = 0;//每页显示多少记录int size = 5;//排序Sort sort = Sort.by(Sort.Direction.DESC, "id");PageRequest pageRequest = PageRequest.of(current, size, sort);User user = new User();user.setAge(2);user.setUsername("user");user.setId(1);Address address = new Address();address.setAddressId(1);user.setAddress(address);ExampleMatcher matcher = ExampleMatcher.matching() //构建对象 .withMatcher("username", ExampleMatcher.GenericPropertyMatchers.contains())// id like %?% .withMatcher("age", ExampleMatcher.GenericPropertyMatchers.exact())//匹配 .withMatcher("addressId", ExampleMatcher.GenericPropertyMatchers.exact())//匹配 .withIgnorePaths("id");//忽略此字段Example example = Example.of(user, matcher);Page all = userJpaRepository.findAll(example, pageRequest);System.out.println("数据:" + all.getContent());System.out.println("当前页码,(此页码第一页为0):" + all.getNumber());System.out.println("总页码数:" + all.getTotalPages());System.out.println("当前页记录数:" + all.getNumberOfElements());System.out.println("总记录数:" + all.getTotalElements());
JpaSpecificationExecutor
JpaSpecificationExecutor这个接口和Repository接口不是一个家族的,它是用对象来写jpql语句的一个接口,内部的操作其实就是Hibernate的CriteriaQuery这种查询方法,这种查询方法,从hibernate开始基本上都很少使用,个人觉得不好用,所以做为了解内容,了解一下就好,JpaSpecificationExecutor接口里有下面的这些方法:
要使用这个方法做查询,UserRepository再继承一个接口:
import com.nanmeishu.springdatajpa.jpademo.entity.User; import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.JpaSpecificationExecutor;public interface UserJpaRepository extends JpaRepository , JpaSpecificationExecutor {}
注意: Java中类是不可以有多个父类的,但是接口是可以有多个父接口的!
List all = userJpaRepository.findAll((root, query, criteriaBuilder) -> { //查询id>10的User记录 Path path=root.get("id"); Predicate predicate = criteriaBuilder.greaterThan(path, 10); return predicate; }); System.out.println(all);}
自定义Repository
spring data除了已经给我们提供了大量的 Repository接口,里边其实已经基本足够我们做一般项目开发中需要的各种数据库查询方法了,但是呢,它仍然考虑到我们可能出现的很个性化的需求,这个提供的方法可能实现不了,所以它支持我们自己定义Repository接口来扩展功能,具体的步骤:
- 定义一个接口
import com.nanmeishu.springdatajpa.jpademo.entity.User;public interface UserDao { User myUserById(int id);}
- 提供UserDao接口的实现类,但是这个类的名字,必须是对应的UserRepository这个接口的名字后面加Impl:
import javax.persistence.EntityManager;import javax.persistence.PersistenceContext;public class UserDaoImpl implements UserDao { //使用jpa技术 @PersistenceContext private EntityManager entityManager; @Override public User myUserById(int id) { String jpsl="select u from User u where u.id=?1"; return entityManager.find(User.class,id); }}
- 回到UserRepository,添加继承的父接口
import com.nanmeishu.springdatajpa.jpademo.dao.UserDao;import com.nanmeishu.springdatajpa.jpademo.entity.User; import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.JpaSpecificationExecutor;public interface UserJpaRepository extends JpaRepository , JpaSpecificationExecutor, UserDao {}
- 就可像使用其他的JpaRepository接口的方法一样使用,userdao里自定义的方法了:
int id=1;User user = userJpaRepository.myUserById(1);