zoukankan      html  css  js  c++  java
  • Spring Data 文档摘译

    如何实现数据库管理操作

    看官方文档给的例子。

    首先定义一个自有的repository接口,继承自Repository接口或者其衍生接口。定义时,注明领域类型和主键类型。然后在接口内声明方法。然后要在配置类上标注@EnableJpaRepositories,让Spring容器自行创建接口的实例。

    /* 
     * 领域类为User, 主键类型是Long
     */
    interface PersonRepository extends Repository<User, Long> {
    	List<Person> findByLastname(String lastname);
    }
    
    
    @EnableJpaRepositories
    public class Config() {}
    

    最后,在需要实现操作的地方直接注入自定义的repository接口就好了。

    public class SomeClient {
    
      @Autowired
      private PersonRepository repository;
    
      public void doSomething() {
        List<Person> persons = repository.findByLastname("Matthews");
      }
    }
    
    

    Repository

    Repository是Spring Data的核心概念,负责管理领域类(domain class)。

    CrudRepository是最基本的Repository接口,实现了增删查改功能。PagingAndSortingReporitory进行了拓展,可以实现结果列表的排序和分页。

    上面的例子中使用了@EnableJpaRepositories注解生成自定义repository接口的实例,如果需要在Spring容器外调用,可以调用工厂生成实例。

    RepositoryFactorySupport factory = …    // 初始化工厂
    UserRepository repository = factory.getRepository(UserRepository.class);
    

    如果想要拓展Spring Data定义好的方法,需要写一个对应接口的实现类。在实现类中用定义自己的方法。实现类名一定要以Impl结尾

    如果覆写的是基础Repository接口方法,需要在@EnableJpaRepositoriesrepositoryBaseClass属性指定对应的实现类。

    如果某些Repository接口不需要在运行时生成对象,可以在接口定义加上注解@NoRepositoryBean

    空指针Null

    Spring Data的crud方法支持返回Java 8的Optional对象,暗示了结果有可能为Null。

    此外,Spring Data还支持三类注解:

    1. @NonNullApi: package级别的注解,表示其下所有方法的参数和返回值都不会是Null
    2. @NonNull: 方法级或参数级注解,前者表示返回值非Null,后者表示参数非Null
    3. @Nullable: 和上一个注解相反,表示返回值或参数可以为Null

    如果使用了第一个注解,就没有必要使用第二个注解。

    对领域事件的支持

    在DDD领域驱动设计模式中,和Repository关联的实体类都是聚合根(aggregate root)。聚合根会发布领域事件。Spring Data对此提供了@DomainEvents注解方便了时间的发布。

    class AnAggregateRoot {
    
        @DomainEvents 
        Collection<Object> domainEvents() {
            // … return events you want to get published here
        }
    
        @AfterDomainEventPublication 
        void callbackMethod() {
           // … potentially clean up domain events list
        }
    }
    
    1. @DomainEvents注解的方法可以返回单个事件,也可以返回多个事件的集合。这个方法不能有入参。
    2. 所有事件发布后,@AfterDomainEventPublication注解的方法可以清理要发布的事件列表。

    QueryDslPredicateExecutor

    继承了QueryDslPredicateExecutor接口的repository接口,可以实现查找满足自定义Predicate对象的结果。

    定义

    interface UserRepository extends CrudRepository<User, Long>, QueryDslPredicateExecutor<User> {
    
    }
    

    调用

    Predicate predicate = user.firstname.equalsIgnoreCase("dave")
    	.and(user.lastname.startsWithIgnoreCase("mathews"));
    
    userRepository.findAll(predicate);
    

    映射

    Spring Data的find方法是可以返回整行数据的。但是通常,程序只需要其中部分字段的数据。对于这种情况,可以使用自定义的接口来完成。

    下面是一个简单的repository接口和领域类例子。

    class Person {
    
      @Id UUID id;
      String firstname, lastname;
      Address address;
    
      static class Address {
        String zipCode, city, street;
      }
    }
    
    interface PersonRepository extends Repository<Person, UUID> {
    
      Collection<Person> findByLastname(String lastname);
    }
    

    如果我们只对其中的firstnamelastname感兴趣,我们可以写一个新的接口。注意:这接口的getter必须是领域类属性的子集。

    interface NamesOnly {
    
      String getFirstname();
      String getLastname();
    }
    

    然后,将repository接口里的方法改成这样。

    interface PersonRepository extends Repository<Person, UUID> {
    
      Collection<NamesOnly> findByLastname(String lastname);
    }
    
    

    查询引擎会在运行时,为每一个查询结果创建接口的代理对象。

    映射接口的支持递归定义。

    interface PersonSummary {
    
      String getFirstname();
      String getLastname();
      AddressSummary getAddress();
    
      interface AddressSummary {
        String getCity();
      }
    }
    

    闭合映射

    闭合映射是指接口的getter总是领域类属性的子集(含全集)。

    Spring Data可以优化使用闭合映射的场景,因为暴露给调用方的属性都是定义的类型。

    开放映射

    接口的getter可以使用@Value注解,来计算新值。

    interface NamesOnly {
    
      @Value("#{target.firstname + ' ' + target.lastname}")
      String getFullName();
      …
    }
    

    在上面这个例子中,注解里面是SpEL表达式。target表示的是整个领域类。这种getter对应的属性在领域类中并不存在,所以称作开放映射。@Value注解里的表达式不能太复杂,毕竟结果是String类型。

    对于简单的表达式,也可以使用default方法。但这要求方法的逻辑只能依赖于映射接口实现的其他getter方法。

    interface NamesOnly {
    
      String getFirstname();
      String getLastname();
    
      default String getFullName() {
        return getFirstname.concat(" ").concat(getLastname());
      }
    }
    
    

    接口方法中的参数也可以在表达式中调用。

    interface NamesOnly {
    
      @Value("#{args[0] + ' ' + target.firstname + '!'}")
      String getSalutation(String prefix);
    }
    

    还有一种更灵活的方法。将定制逻辑在其他Spring Bean上实现出来,然后在SpEL表达式中实现出来。看个例子。

    @Component
    class MyBean {
    
      String getFullName(Person person) {
        …
      }
    }
    
    interface NamesOnly {
    
      @Value("#{@myBean.getFullName(target)}")
      String getFullName();
      …
    }
    

    映射类

    Spring Data也支持DTO类型的实体类来映射。DTO类属性为领域类属性的子集。

    当然,使用DTO的时候,运行时不会创建代理对象。

    Spring Data在优化查询字段的时候,只会选择构造器的参数。(这个听上去比接口靠谱哇)

    class NamesOnly {
    
      private final String firstname, lastname;
    
      NamesOnly(String firstname, String lastname) {
    
        this.firstname = firstname;
        this.lastname = lastname;
      }
    
      String getFirstname() {
        return this.firstname;
      }
    
      String getLastname() {
        return this.lastname;
      }
    
      // equals(…) and hashCode() implementations
    }
    

    动态映射

    如果想要在调用的时候再决定返回类型,可以这样做。

    interface PersonRepository extends Repository<Person, UUID> {
    
      Collection<T> findByLastname(String lastname, Class<T> type);
    }
    
    /*
     *  动态的传入返回类型
     */
    void someMethod(PersonRepository people) {
    
      Collection<Person> aggregates =
        people.findByLastname("Matthews", Person.class);
    
      Collection<NamesOnly> aggregates =
        people.findByLastname("Matthews", NamesOnly.class);
    }
    
    

    Example查询

    Example查询可以动态的创建查询,而不需要指定所查询的字段。

    首先,得有一个领域类。

    public class Person {
    
      @Id
      private String id;
      private String firstname;
      private String lastname;
      private Address address;
    
      // … getters and setters omitted
    }
    

    有了领域类对象,就可以创建Example对象了。默认的,值为Null的属性会被忽视掉。Example对象可以使用of工厂方法来创建,也可以用ExampleMatcher来创建。

    Person person = new Person();                         
    person.setFirstname("Dave");                          
    Example<Person> example = Example.of(person);
    

    调用需要继承QueryByExampleExecutor接口。

    public interface QueryByExampleExecutor<T> {
    
      <S extends T> S findOne(Example<S> example);
    
      <S extends T> Iterable<S> findAll(Example<S> example);
    
      // … more functionality omitted.
    }
    
    

    调用接口方法,就可以查询满足指定条件的结果。

    ExampleMatcher可以实现更多条件的String类型的查询。

    审计

    Spring Data 可以通过注解或者接口实现复杂的审计功能,获知谁在何时创建了(或修改了)一条数据。

    注解实现

    注解有@CreatedBy@LastModifiedBy@CreatedDate@LastModifiedDate可选。时间可以是Date类型,也可以是Datetime类型,也支持long类型。

    class Customer {
    
      @CreatedBy
      private User user;
    
      @CreatedDate
      private DateTime createdDate;
    
      // … further properties omitted
    }
    

    接口实现

    可以考虑让领域类实现Auditable接口,也可以继承AbstractAuditable抽象类。

    文档更推荐注解实现,因为这样更直观,也更灵活。

    AuditorAware

    提供了可以让服务获知当前用户信息的接口。

  • 相关阅读:
    Java8中findAny和findFirst的区别
    Lombok使用与原理
    java.util.ConcurrentModificationException异常原因及解决方法
    PacketTooBigException问题解决
    数据库中空字符串和NULL值两个概念的区别
    Java8采用stream、parallelStream迭代的区别
    Spring四大注解
    @Qualifier的作用和应用
    @resource和@autowired的区别是什么
    @Transactional注解详细用法
  • 原文地址:https://www.cnblogs.com/rim99/p/8452878.html
Copyright © 2011-2022 走看看