小言_互联网的博客

SpringData技术

458人阅读  评论(0)

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. 索引参数:索引值从1开始,查询中 ”?X” 个数需要与方法定义的参数个数相一致,并且顺序也要一致。
  2. 表名需要使用POJO代替,并且用别名。
  3. 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的注解就相当于外键列。

AddressRepository接口:
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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场