SpringData技术
1. 什么是SpringData
Spring Data :Spring 的一子项目。用于简化数据库访问,支持NoSQL 和关系数据存储。其主要目标是使数据库的访问变得方便快捷。
SpringData 项目所支持 NoSQL 存储:
- MongoDB (文档数据库)
- Neo4j(图形数据库)
- Redis(键/值存储)
- Hbase(列族数据库)
SpringData 项目所支持的关系数据存储技术:
- JDBC
- JPA
Spring Data JPA : 致力于减少数据访问层 (DAO) 的开发量. 开发者唯一要做的,就只是声明持久层的接口(有点类似于mybatis采用接口代理方式),其他都交给 Spring Data JPA 来帮你完成!
框架怎么可能代替开发者实现业务逻辑呢?比如:当有一个 UserDao.findUserById() 这样一个方法声明,大致应该能判断出这是根据给定条件的 ID 查询出满足条件的 User 对象。Spring Data JPA 做的便是规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。
2、SpringData JPA 入门
2.1、SpringData 开发步骤
使用 Spring Data JPA 进行持久层开发需要的四个步骤:
-
配置 Spring 整合 JPA
-
在 Spring 配置文件中配置 Spring Data:
让 Spring为声明的接口创建代理对象。配置了jpa:repositories后,Spring初始化容器时将会扫描base-package 指定的包目录及其子目录,为继承Repository或其子接口的接口创建代理对象,并将代理对象注册为Spring Bean,业务层便可以通过 Spring自动封装的特性来直接使用该对象。
-
声明持久层的接口,该接口继承Repository:
Repository 是一个标记型接口,它不包含任何方法,如必要,Spring Data 可实现 Repository 其他子接口,其中定义了一些常用的增删改查,以及分页相关的方法。
-
在接口中声明需要的方法:
Spring Data 将根据给定的策略(具体策略稍后讲解)来为其生成实现代码。
2.2、SpringData环境搭建
创建maven项目:
2.3、入门案例
2.3.1、Lombok插件
开发中我们要写大量的java实体类,虽然idea能够直接生成get和set方法,有的时候碰到那种属性很多的实体类,看代码都看的头痛。Lombok插件,它能够让代码更简洁好看,不需要生成get和set方法,编译也能通过。更加直白的说,就是帮助我们快速生成get和set方法。
同时还需要在pom中引入
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
上面这些步骤做完之后,只要在书写的pojo上添加对应的注解即可:@Data
注解
import lombok.Data;
@Data
public class User {
private Integer id;
private String username;
private String password;
}
这样在编写pojo的时候,只需要添加属性即可, 其他的get和set自动生成(在源码中是没有get和set的,只是在生成的class源码中存在)。
需要在数据库中创建User表
CREATE TABLE users(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(100) NOT NULL UNIQUE,
PASSWORD VARCHAR(100) NOT NULL
);
2.3.2、编写pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.neuedu.sssp</groupId>
<artifactId>sssp</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<!-- Add typical dependencies for a web application -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-data-jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- Package as an executable jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.3.3、编写springboot的配置文件
server:
port: 80
# 数据库相关配置
spring:
jpa:
generate-ddl: false
show-sql: true
hibernate:
ddl-auto: none
# 配置数据源
datasource:
url: jdbc:mysql://127.0.0.1:3306/stumgr
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
# 日志
logging:
level:
root: INFO
org.hibernate: INFO
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
org.hibernate.type.descriptor.sql.BasicExtractor: TRACE
com.springms: DEBUG
2.3.4、编写启动类
/**
* @author yyang
*/
@SpringBootApplication
@EnableJpaRepositories("com.neuedu.repository") // JPA扫描该包路径下的Repositorie
@EntityScan("com.neuedu.pojo") // 扫描Entity(pojo)实体类
public class SSSPStart {
public static void main(String[] args) {
SpringApplication.run(SSSPStart.class,args);
}
}
2.3.5、编写Controller类
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
@RequestMapping("find")
public Iterable<User> findAllUser(){
return userService.findAllUser();
}
}
2.3.6、编写Service
接口的编写:
/**
* @author yyang
*/
public interface IUserService {
public Iterable<User> findAllUser();
}
编写实现类:
/**
* @author yyang
*/
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private UserRepository userRepository;
@Override
public Iterable<User> findAllUser() {
return userRepository.findAll();
}
}
2.3.7、编写repository
/**
* @author yyang
*/
public interface UserRepository extends JpaRepository<User,Integer> {
}
关于Spring Data JPA的Repository接口后续会详细说明
2.3.8、完整的pojo
@Data // 这里使用的插件自动生成get和set
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue
private Integer id;
private String username;
private String password;
}
2.3.9、测试结果
3、Repository接口
Repository 接口是 Spring Data 的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法
@Indexed
public interface Repository<T, ID> {
}
Spring Data可以让我们只定义接口,只要遵循 Spring Data的规范,就无需写实现类。
spring data 支持的关键字
可以直接到https://docs.spring.io/spring-data/jpa/docs/2.1.9.RELEASE/reference/html/ 查询
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 |
… 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<Age> ages) |
… where x.age in ?1 |
NotIn |
findByAgeNotIn(Collection<Age> ages) |
… 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) |
/**
* @author yyang
*/
public interface PersonRepository extends Repository<Person,Integer> {
// 根据姓名以指定内容开始模糊查询和password等于指定的数据
// where name like name% and age = ?
public List<User> getByUsernameStartingWithAndPasswordEquals(String uesrname, String age);
}
4、Repository子接口
基础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。它们的继承关系如下:
-
Repository: 仅仅是一个标识,表明任何继承它的均为仓库接口类
-
CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法
-
PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法
-
JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法
-
自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。
-
JpaSpecificationExecutor: 不属于Repository体系,实现一组 JPA Criteria 查询相关的方法
5、使用 @Query
注解
5.1 使用数字占位符
可以在 Repository方法中,将查询直接在相应的接口方法上(类似于mybatis的注解开发),结构更为清晰。
@Query("select u from User u where u.id = ?1")
public User findUserById(Integer id);
说明:
- 索引参数:索引值从1开始,查询中 ”?X” 个数需要与方法定义的参数个数相一致,并且顺序也要一致。
- 表名需要使用POJO代替,并且用别名。
- select后面不能使用*,可以使用别名代替
5.2 使用参数占位符和注解
上面使用的数字占位符非常不友好,可以使用参数占位符代替,但是需要在方法上使用@Param注解声明参数占位符的名称,
@Query("select u from User u where u.username = :name")
public User findUserByName(@Param("name") String name);
@Param 注解中标注的参数名。
如果是@Query中有LIKE关键字,后面的参数需要前面或者后面加 %,这样在传递参数值的时候就可以不加 %:
-
@Query(“select o from UserModel o where o.name like ?1%”)
public List<UserModel> findByUuidOrAge(String name);
-
@Query(“select o from UserModel o where o.name like %?1”)
public List<UserModel> findByUuidOrAge(String name);
-
@Query(“select o from UserModel o where o.name like %?1%”)
public List<UserModel> findByUuidOrAge(String name);
还可以使用@Query来指定本地查询,只要设置nativeQuery为true,比如:
@Query(value="select * from tb_user where name like %?1" ,nativeQuery=true) public List<UserModel> findByUuidOrAge(String name);
6、@Modifying 注解和事务
@Query与@Modifying执行(CUD)操作
这两个annotation一起声明,可定义个性化更新操作,例如只涉及某些字段更新时最为常用,示例如下:
@Modifying
@Query("update users set username =:name where id = :id")
public Integer updateUserName(@Param("name") String name , @Param("id") Integer id);
注意:在调用的地方必须加事务,没有事务不能正常执行
对于自定义的方法,如需改变 SpringData 提供的事务默认方式,可以在方法上注解@Transactional声明。
7、CrudRepository接口
CrudRepository接口提供了最基本的对实体类的添删改查操作
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S var1);
<S extends T> Iterable<S> saveAll(Iterable<S> var1);
Optional<T> findById(ID var1);
boolean existsById(ID var1);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> var1);
long count();
void deleteById(ID var1);
void delete(T var1);
void deleteAll(Iterable<? extends T> var1);
void deleteAll();
}
8、PagingAndSortingRepository接口
该接口提供了分页与排序功能
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort var1);
Page<T> findAll(Pageable var1);
}
9、JpaRepository接口
该接口提供了JPA的相关功能
@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);
}
10、JpaSpecificationExecutor接口
不属于Repository体系,实现一组JPA Criteria 查询相关的方法
public interface JpaSpecificationExecutor<T> {
Optional<T> findOne(@Nullable Specification<T> var1);
List<T> findAll(@Nullable Specification<T> var1);
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
long count(@Nullable Specification<T> var1);
}
Specification:封装 JPA Criteria 查询条件。通常使用匿名内部类的方式来创建该接口的对象
public List<User> findUserByAge(Integer age) {
/**
* Root : 本次条件查询时对应的实体类POJO,通过root来编写条件中对应的列名
* CriteriaBuilder : 构建查询的条件对象Predicate
* CriteriaQuery : 封装查询的相关内容
*/
Specification spec = new Specification<User>(){
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.greaterThan( root.get("age") , age );
}
};
return this.userRepository.findAll(spec);
}
11、多表操作
11.1、一对一查询
一对一的业务场景:用户和地址,一个用户拥有唯一的户籍归属地
@Data
@ToString
@Entity
@Table(name = "tb_addr")
public class Address implements Serializable {
@Id
@GeneratedValue
private Integer id;
private String address;
@OneToOne
@JoinColumn(name="uid" , referencedColumnName ="id")
private User uesr;
}
针对一对一的查询操作,需要在对应的一的pojo中添加另外一方的对象,并且使用
@OneToOne
@JoinColumn(name=“uid” , referencedColumnName =“id”)
注解配置映射关系。name=“uid” 当前的外键列名,referencedColumnName =“id”,关联的对象的中的主键名称
在pojo中如果存在外键,需要删除或者忽略。而配置的OneToOne的注解就相当于外键列。
Address的Repository接口:
public interface AddressRepository extends JpaRepository<Address , Integer> {
public List<Address> findByAddress(String address);
}
UserRepository接口
public interface UserRepository extends Repository<User,Integer> {
public User findById(Integer id);
}
需要在接口中提供对应的查询方法。
11.2、一对多查询
一对多业务场景假设:作者和作品,一个作者对应多个作品,每个作品对应唯一的一个作者。
11.2.1 Book和User实体
/**
* @author yyang
*/
@Data
@ToString
@Entity
@Table(name = "tb_book")
public class Book implements Serializable {
@Id
@GeneratedValue
private Integer id;
private String name;
private Double price;
private Integer uid; // 是外键
}
/**
* @author yyang
*/
@Data
@ToString
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue
private Integer id;
private String username;
private String password;
@OneToMany
@JoinColumn(name = "uid" ,referencedColumnName = "id")
private List<Book> books;
}
针对一对多的操作:
在一的一方的Pojo中添加多的一方的引用:List集合,同时需要在属性添加对应的注解
@OneToMany : 声明当前的属性是一个一对多的设置,SpringData JPA会进行关联查询
@JoinColumn(name = “uid” ,referencedColumnName = “id”) 设置关联的外键和主键的关系
11.2.2 BookRepository
/**
* @author yyang
*/
public interface BookRepository extends Repository<Book ,Integer> {
public Book findById(Integer id);
public Book findByUid(Integer uid);
}
11.3、 多对多查询
11.3.1 表结构
多对多场景假设:订单和商品之间的关系,一个订单中存在多类商品,一类商品可以出现在多个订单中。
11.3.2 POJO的编写
/**
* 商品表
*/
@Entity
@Table(name = "tb_item")
public class Item {
@Id
@GeneratedValue
@Getter@Setter
private Integer id;
@Getter@Setter
private String itemName;
@Getter@Setter
private Float itemPrice;
@Getter@Setter
private String itemDetail;
@ManyToMany(targetEntity=Order.class,mappedBy="items")
private List<Order> orders;
}
@Data
@Entity
@Table(name = "tb_order")
public class Order {
@Id
@GeneratedValue
private Integer id;
private Long userId;
private String orderNumber;
@ManyToMany
@JoinTable(name="tb_orderdetail",
joinColumns = {
@JoinColumn(name="order_id",referencedColumnName = "id")},
inverseJoinColumns = {
@JoinColumn(name="item_id",referencedColumnName="id")})
private List<Item> items;
}
在多对多查询的时候,分为主表和副表(主查询对象和次查询对象)
针对上面的案例:
Order是主查询对象,Item是次查询对象,但在两个POJO中都需要使用集合表示多的这种关系。同时在上面添加@ManyToMany注解,表示多对多
在主查询Order中:
需要使用@JoinTable 声明多对多的中间和字段映射关系
@JoinTable(name=“tb_orderdetail”,
joinColumns = { @JoinColumn(name=“order_id”,referencedColumnName = “id”)},
inverseJoinColumns = {@JoinColumn(name=“item_id”,referencedColumnName=“id”)})@JoinTable注解中的name书写中间表名
joinColumns书写主查询的主键在中间表中的外键列名
referencedColumnName 书写主查询的主键列名(可以省略)
inverseJoinColumns 书写关联的次查询在中间表中的外检列名
次查询Item中:
需要在集合属性上的@ManyToMany注解中使用
targetEntity属性编写主查询的类
mappedBy属性编写主查询管理的次查询的属性名
千万别踩的坑:
在给主查询和次查询的pojo中添加get和set方法的时候,注意不要形成嵌套。
在次查询中的集合不要再生成get和set方法了,否则就会嵌套,无法正常进行数据的json操作。
转载:https://blog.csdn.net/qq_42483764/article/details/117456591