SpringDataMongoDB-2

SpringDataMongoDB-2,第1张

SpringDataMongoDB-2 定义Repository接口

这是使用SpringData MongoDB的第一步,先定义 Repository。这个接口需要传递两个类型参数,域对象和主键的类型。就像上一节例子的那样。

增加顶级接口

一般来说都是直接继承Repository或者CrudRepository,或者具体的SpringData各个模块自己实现的Repository的子类。但是如果自己想要做一个顶级的接口,项目中的各个Repository继承与它来做处理。需要按照下面的步骤来 *** 作。

  1. 自定义接口,增加方法,继承于Repository.

  2. 再接口上面标注@NoRepositoryBean,表示不会为这个接口创建对应的bean。

不继承Repository接口

如果不想继承Repository接口和它的子接口。可以自己写接口,标注@RepositoryDefinition(domainClass = Book.class, idClass = String.class)注解,想要用CrudRepository接口中的哪些方法,直接拷贝过来就行。

@RepositoryDefinition(domainClass = Book.class, idClass = String.class)
public interface BookCustomerRepository {
    // 这俩都是从CrudRepository中拷贝过来的,只是将泛型去掉,变为实际的类型
    Optional<Book> findById(String id);
     Iterable<Book> saveAll(Iterable<Book> entities);
}

测试类如下:

@SpringBootTest(classes = SpringdataMongodbApplication.class)
public class EtcTest {
    @Autowired
    private BookCustomerRepository bookCustomerRepository;

    @Test
    public void testCustomerInterface(){
        ArrayList<Book> param = new ArrayList<>();
        List<String> bookName = Arrays.asList("java", "go", "php", "c", "c++", "Mysql");
        ThreadLocalRandom current = ThreadLocalRandom.current();
        LocalDate today = LocalDate.now();
        for (int i = 0; i < 1000; i++) {
            String name = bookName.get(current.nextInt(bookName.size()));
            Book book = new Book().setCount(current.nextInt(1000))
                    .setName(name)
                    .setPrice(current.nextDouble(100))
                    .setPublishTime(today.minusDays(current.nextInt(30)))
                    .setCount(current.nextInt(1000))
                    .setShortName(name + ":" + name);
            param.add(book);
        }
        // 先保存,保存返回一个可以迭代对象
        Iterable<Book> books = bookCustomerRepository.saveAll(param);
        Book book = books.iterator().next();// 拿到第一条数据,去查看,看能够查到
        Optional<Book> bookOptional = bookCustomerRepository.findById(book.getId());
        Assert.isTrue(bookOptional.isPresent());
    }
}
SpringData多个模块一块使用

SpringData有多个模块,可能会一块使用,比如一个项目里面有Spring Data JPA,Spring Data MongoDB,Spring Data Redis。要是只用一个模块还好,所有的repository只会关联到这一个。但是多个一块工作的时候,就需要将他们区分,当探测到classpath上有多个Spring Data模块,SpringData就会进入严格的配置模式。

需要通过下面三种方式来区分

  1. 继承每个模块专有的repository,比如继承JpaRepository或者MongoRepository,这样的区分就很明显,前者是JPA,后者是MongoDB。
  2. 使用每个模块专有的注解,比如@Entity@Document。前者是JPA,后者是MongoDB。
  3. 再配置类上使用@Enable${store}Repositories注解,比如EnableJpaRepositoriesEnableMongoRepositories,可以利用他们来指定具体的扫描的包,比如@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")表示这个包下面使用的是jpa。
定义查询方法

查询方法是日常使用中变种最多的,用途最广的。别的方法在CrudRepository中定义了,基本也够用。

可以在自定义的Repository中写方法,这些方法不需要自己实现,只要满足SpringData的语法规则,它会自己帮我们实现

  1. 实体类
@Document
@Data
@Accessors(chain = true)
@ToString
public class Book {
    private String id;
    @Field(name = "name_1") // 这里我用@Field做映射了, 
    private String name;
    private String shortName;
    private Integer count;
    private Double price;
    private LocalDate publishTime;
}
  1. Repository
public interface BookQueryMethodRepository extends MongoRepository<Book, String> {
    // 通过名字来查询student,
    Book findBookByName(String name);

    // 通过name和Count查询,要注意,参数位置是对应的
    List<Book> findBooksByNameAndCount(String name,int count);
    // name like 并且 count > 
    Optional<List<Book>> findBooksByNameLikeAndCountGreaterThan(String name, int count);
    // count in and name=
    List<Book> findDistinctByCountInAndNameIs(List<Integer> count,String name);
    // 忽略大小写
    List<Book> findByNameIgnoreCase(String name);
}
  1. 测试类
@SpringBootTest(classes = SpringdataMongodbApplication.class)
public class BookQueryMethodRepositoryTest extends BaseApplicationTests {
    @Autowired
    BookQueryMethodRepository bookQueryMethodRepository;

    @Test
    public void testSave() {
        ArrayList<Book> param = new ArrayList<>();
        List<String> bookName = Arrays.asList("java", "go", "php", "c", "c++", "Mysql");
        ThreadLocalRandom current = ThreadLocalRandom.current();
        LocalDate today = LocalDate.now();
        for (int i = 0; i < 1000; i++) {
            String name = bookName.get(current.nextInt(bookName.size()));
            Book book = new Book().setCount(current.nextInt(1000))
                    .setName(name)
                    .setPrice(current.nextDouble(100))
                    .setPublishTime(today.minusDays(current.nextInt(30)))
                    .setCount(current.nextInt(1000))
                    .setShortName(name + ":" + name);
            param.add(book);
        }
        bookQueryMethodRepository.saveAll(param);
    }

    @Test
    public void testDelete() {
        bookQueryMethodRepository.deleteAll();
    }

    @Test
    public void testFind1() {
        Book book = bookQueryMethodRepository.findBookByName("c++");
        System.out.println(book);
    }

    @Test
    public void testFind2() {
        List<Book> booksByName = bookQueryMethodRepository.findBooksByNameAndCount("c++", 10);
        booksByName.forEach(System.out::println);
    }

    @Test
    public void testFind3() {
        Optional<List<Book>> booksOption = bookQueryMethodRepository.findBooksByNameLikeAndCountGreaterThan("ja", 10);
        booksOption.ifPresent(System.out::println);
    }

    @Test
    public void testFind4() {
        List<Book> goBooks = bookQueryMethodRepository.findDistinctByCountInAndNameIs(Arrays.asList(360, 802), "go");
        goBooks.forEach(System.out::println);
    }

    @Test
    public void testFind5() {
        List<Book> goBooks = bookQueryMethodRepository.findByNameIgnoreCase("GO");
        goBooks.forEach(System.out::println);
    }
}
  1. 配置文件

    spring:
      application:
        name: mongoDB-test
    
      data:
        mongodb:
          username: root
          password: root
          authentication-database: admin
          host: localhost
          port: 27017
          database: test
    
    server:
      port: 8080
    

建议可以对比MongoDB的查询语句的结果来验证是否正确

方法名称规则说明

方法名称可以分为对象和判断条件,第一部分(find…by,exists…By)定义了这次查询的对象,在find和by或者其他的类似关键字中,除了关键字之外,别的都是描述性的。

从By之后,后面的一部分为判断条件,在这些判断条件之间可以用And或者Or联系起来。

具体的可以看:

Repository query keywords

Supported query method predicate keywords and modifiers

上面不确定的时候查一查,有一些基本的规则知道的话,写代码是没有问题的,此外Idea还有提示。

  1. 方法名称中的判断条件是属性串联关系组成的,比如可以将属性的表达式用And或者Or来连接在一起,方法的参数和什么条件查询的顺序对应。对于不同类型的属性还可以用不同的 *** 作符,比如Between,LessThan,GreaterThan,这只是语法定义规则,具体的实现还得看不同模块。

  2. 查询方法常用几个关键词开头find…By,query…By,,get…By。

  3. 还可以在属性增加OrderBy来指定排序,后面接Asc或者Desc表示升降序。可以在find或者get、query之后用Top或者First来限制返回的数量,比如Top10,返回的是前十条,如果只是一个First,默认返回一条。例子如下:

      // 查找name 通过count升序排序,取前10个
        List<Book> findTop10ByNameOrderByCountAsc(String name);
    

    测试类

      public class BookQueryMethodRepositoryTest extends BaseApplicationTests {
       @Autowired
        BookQueryMethodRepository bookQueryMethodRepository;
        @Autowired
        MongoTemplate mongoTemplate; // 提供的很方便的 *** 作MOngoDB的工具类。	
    
    	@Test
        public void testFind6() {
            // 通过mongoTemplate来做查询,用来做对比
            Criteria criteria = Criteria.where("name_1").is("go");
            Query query = Query.query(criteria)
                     // 通过count升序排序
                    .with(Sort.sort(Book.class).by(Book::getCount).ascending())
                    .limit(10);
            List<Book> books = mongoTemplate.find(query, Book.class);
    		 // 名字是go,count升序排序,limit 10个
            List<Book> go = bookQueryMethodRepository.findTop10ByNameOrderByCountAsc("go");
            int index = 0;
            while (index < books.size()) {
                Assert.isTrue(books.get(index).getId().equals(go.get(index).getId()));
                index++;
            }
            Assert.isTrue(index == books.size() && index == go.size());
        }
      }
    

    对应的MongDO查询语句

    db.book.find(
        {
            name_1:"go"
        }
    ).sort(
    {
        count:1
    }
    ).limit(10)
    
  4. 还可以增加Pageable ,Sort 参数来做查询和分页。返回值可以是Page,Slice,List

    public interface BookQueryMethodRepository extends MongoRepository<Book, String> {
        Slice<Book> findBooksByName(String name, Pageable pageable);
    }
    

    测试类

    	 @Test
        public void testFind7() {
            //PageRequest聚合了sort
            Sort sort = Sort.sort(Book.class)
                    .by(Book::getCount)
                    .ascending();
            PageRequest request = PageRequest.of(1, 10, sort);
            Slice<Book> books = bookQueryMethodRepository.findBooksByName("go", request);
            books.get().forEach(System.out::println);
        }
    

    对应的MongDO查询语句

    db.book.find(
    {name_1:"go"}
    )
    .sort({count:1})
    .skip(10).limit(10);
    

​ Page和Slice的区别:

page对象知道元素的总数和页数,他是通过一个基础的查询计数来计算的,所以它比较费时间.

Slice只知道下一个Slice是否可用,在返回大量数据集合的时候就比较方便了.
查询方法的返回值

大体的分为下面几种:(具体的可以看Supported Query Return Types)

  1. 返回集合或者可以迭代的对象

​ 查询的方法支持Java原生的Iterable,List,Set,同时也支持Spring的Streamable,Iterable的实现类,还可以返回 Vavr

// 返回collection
    Collection<Book> findByNameEndingWith(String name);
    // 返回Spring的Streamable
    Streamable<Book> findByShortNameEndsWith(String name);
    // 返回自己包装的Streamable的实现类
    BookStream findByPriceLessThanEqual(double price);

注意说明

  • Streamable是Spring提供的一个函数式接口,通过它可以很方便的聚合,过滤.

  • BookStream实现了Streamable接口,增加了一些自定义的方法,想要这样用的话,需要暴露一个构造函数或者静态工厂方法将Streamable作为参数传递进去,方法的名字是of(…)或者valueOf(…).下面是我自己实现的代码举例

    @RequiredArgsConstructor(staticName = "of") //lombok注解,提供静态方法,名字是of
    public class BookStream implements Streamable<Book> {
        private final Streamable<Book> streamable;
      
        @Override
        public Iterator<Book> iterator() {
            return streamable.iterator();
        }
      
        public int getTotal() {
            return streamable.stream()
                    .map(Book::getCount)
                    .reduce(0, Integer::sum);
        }
    }
    

测试类

   @Test
    public void testFind9(){
      Streamable<Book> bookStreamable = bookQueryMethodRepository.findByShortNameEndsWith("o");
        Collection<Book> bookStreamable1 = bookQueryMethodRepository.findByNameEndingWith("va");
        Streamable<Book> streamable = bookStreamable.and(bookStreamable1); //聚合

        //通过shortname分组,看看有多少个
        Map<String, List<Book>> collect = streamable.stream()
                .collect(Collectors.groupingBy(Book::getShortName));
        collect.forEach((key, value) -> {
            System.out.println(key);
            System.out.println(value.size());
        });
    }
   @Test
    public void testFind10(){
        // 自己增加了getTotal的方法
        BookStream bookStream = bookQueryMethodRepository.findByPriceLessThanEqual(10);
        System.out.println(bookStream.getTotal());
    }

testFind10对应的MongoDB的语法

db.book.aggregate(
[
    {
       $match:{
            price:{$lte:10}
       }
    },
    {
       $group:{
        _id:null,
        count:{$sum:"$count"}
       }
    }
]
)
  1. 返回Optional或者Option

    所有CRUD的方法都支持返回java8中的Optional,同样也支持如下的几个类型

    • com.google.common.base.Optional
    • io.vavr.control.Option
    • scala.Option

    注意

    查询方法可以选择不适用任何的包装的类型,没有结果就直接返回null,但是对于返回collections,或者collection的包装类,streams没有结果不会返回null.

        Book findByNameIsAndCountGreaterThanAndPriceIs(String name,int count,double price);
    

    测试类

      @Test
        public void testFind11(){
            Book book = bookQueryMethodRepository.findByNameIsAndCountGreaterThanAndPriceIs("小红", 12, 12); // 这肯定是没有的.返回的是null
            Assertions.assertNull(book);
        }
    
  2. 返回异步对象

    返回类型可以是FutureCompletableFutureListenableFuture,需要用@Async注解.实际上会将这个查询 *** 作提交个Spring TaskExecutor.然后立即返回.

    // 增加ListenableFuture
    	@Async
        ListenableFuture<List<Book>> findByNameLike(String name);
    

    测试类

        @Test
        public void testFind12(){
            ListenableFuture<List<Book>> goFuture = bookQueryMethodRepository.findByNameLike("go");
            goFuture.addCallback(new ListenableFutureCallback<List<Book>>() {
                @Override
                public void onFailure(Throwable ex) {
                    ex.printStackTrace();
                }
    
                @Override
                public void onSuccess(List<Book> result) {
                  Assert.notEmpty(result);
                }
            });
        }  
    
  3. 返回单个对象

    在上面的例子中已经说了,这里就不再说了.

  4. 返回Page,Slice对象.

    上面已经说了,这里就不再说了.

  5. 返回Stream对象.

    可以返回Java8的Stream对象.按照递增的方式来处理.

    Stream<Book> findByCountGreaterThanEqualAndNameIs(int count,String name);
    

    测试类

      @Test
        public void testFind13(){
            try (Stream<Book> goStream = bookQueryMethodRepository.findByCountGreaterThanEqualAndNameIs(20, "go")){
                System.out.println(goStream.count());
            }
        }
    

    Stream要记得关

删除方法

相比查询,删除和更新就比较简单了,除了CrudRepository提供的一些方法之外,它也是可以像查询方法一样,自定义方法签名,SpringData-MongoDB帮我们实现.对于删除 *** 作是以remove或者delete开头的

 // 返回删除对象,要注意,如果返回值不是一个,这个方法就会报错
    Book deleteByIdIs(String id);
    // 返回删除的行数
    int removeById(String id);
    // 返回删除的对象的集合。
    List<Book> removeBookByNameIs(String name);

测试类

   @Test
    public void testDelete1(){
        Book book = bookQueryMethodRepository.findByNameIgnoreCase("go").get(0);
        Book book1 = bookQueryMethodRepository.deleteByIdIs(book.getId());
        Assertions.assertEquals(book1,book);
    }
     @Test
    public void testDelete2(){
        //先查用来做比对
        List<Book> book = bookQueryMethodRepository.findByNameIgnoreCase("java");
        List<Book> books= bookQueryMethodRepository.removeBookByNameIs("java");
        Assertions.assertArrayEquals(book.toArray(new Book[]{}),books.toArray(new Book[]{}));
    }

需要注意,如果查询的结果不是唯一的,但返回值确实唯一的,比如返回值是Book,那这个方法会报错

更新

更新 *** 作在CrudRepository接口中并没有定义,但是它的Save方法却有替换的功能,如果_id字段一样,就会替换掉.在后面的文章中会介绍MongoTemplate的使用,它里面提供了很多的方法.

  @Test
    public void testUpdate1() {
        // 找一条数据,更新一哈
        List<Book> byNameIgnoreCase = bookQueryMethodRepository.findByNameIgnoreCase("c++");
        Book book = byNameIgnoreCase.get(0)
                .setName("c++ =====+1");
        // 直接保存
         bookQueryMethodRepository.save(book);
		// 再次查找,看id是否一致
        List<Book> res = bookQueryMethodRepository.findByNameIgnoreCase("c++ =====+1");
        Assert.isTrue(Objects.equals(res.get(0).getId(), book.getId()));
    }

SpringData MongoDB中接口中定义方法介绍的差不多了,这些方法都不需要我们手动来实现,SpringData MongoDB会自己帮我们实现.除了这些方法之外,他还提供了MongoTemplate,他更加的灵活.后续的文章会介绍如果使用MongoTemplate,如果自定义Repository,如果通过Example来查询,如果做聚合 *** 作,创建索引等等…


关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://www.outofmemory.cn/langs/788756.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-05
下一篇 2022-05-05

发表评论

登录后才能评论

评论列表(0条)

保存