我想从JPA Repository创建一个Stream。目标是将来自Repo(可能超过一百万)的实体映射到其他实体,而这些实体又将存储在另一个Repo中。
到目前为止,我构建了一个收集器,它将收集给定数量(例如1000个)的实体,然后将它们存储到目标回购中。这将在并行流中工作。我现在需要的是从源Repo获取实体的好方法,并在需要时将它们提供给Stream。
到目前为止,最有希望的是实现供应商(http://docs.oracle.com/javase/8/docs/api/java/util/function/Supplier.html)以通过生成来构建流,但是当查询源Repo不提供另一个实体时,我没有找到终止流程的方法。 / p>
任何指针?
答案 0 :(得分:2)
我们最近在最新的Spring Data Fowler Release Train的RC1版本中添加了对Spring Data JPA(和MongoDB)的支持。
Example for Stream returned by a delegating default method Example for "real streaming" Stream
答案 1 :(得分:1)
如果您能够将来源表达为Supplier
,那么您也可以实施Spliterator
。如果有新项目或返回Supplier.get
,您将实施boolean tryAdvance(Consumer)
而不是accept
而不会返回新值,而是在Consumer
上调用false
。除此以外。对于大多数情况,与Iterator
相比,这简化了实现,您必须处理可以按任意顺序调用的两个方法hasNext
和next
。
您必须为Spliterator
实现更多方法,但幸运的是,有直接的方法可以实现它们。
public Spliterator<T> trySplit() {
return null;// simple answer when splitting is not supported
}
public long estimateSize() {
return Long.MAX_VALUE; // the value which should be used for UNKNOWN
}
public int characteristics() {
return 0; // no flags but check out whether some flags fit
}
对于characteristics
方法,值得查找the possible values,因为如果它们符合您的源特征,它们可能会改进流处理。
获得Spliterator
后,您可以创建一个流:
Stream<T> s=StreamSupport.stream(sp, false);
如果您的来源更符合hasNext
/ next
模式,您可以实现普通Iterator
并让JRE创建Spliterator
,如{{3}中所述}}
答案 2 :(得分:0)
一个简单的例子可能是:
@Repository
public class MyEntityRepository extends CrudRepository<MyEntity, Long> {
}
@Component
public class MyEntityService {
@Autowired
private MyEntityRepository myEntityRepository;
public void() {
// if the findAll() method returns List
Stream<MyEntity> streamFromList = myEntityRepository.findAll().stream();
// if the findAll() method returns Iterable
Stream<MyEntity> streamFromIterable = StreamSupport.stream(myEntityRepository.findAll().spliterator(), true);
}
}
答案 3 :(得分:0)
好的,感谢所有的贡献。我将所说的内容与我所需要的内容相结合。也许实施将澄清我想要的开始。
我创建了两个类, RepositryCollector 和 RepositorySpliterator 。
public class RepositoryCollector<T> implements Collector<T, Tuple2<Integer,List<T>>, Integer>{
private JpaRepository<T, ?> repository;
private int threshold;
public BinaryOperator<Tuple2<Integer, List<T>>> combiner() {
return (listTuple, itemsTuple) -> {
List<T> list = listTuple._2;
List<T> items = itemsTuple._2;
list.addAll(items);
int sum = listTuple._1 + itemsTuple._1;
if(list.size() >= this.threshold){
this.repository.save(list);
this.repository.flush();
list = new LinkedList<>();
}
return new Tuple2<>(sum, list);
};
}
}
我省略了收集器所需的其他功能,因为组合器中存在所有相关信息。同样适用于Spliterator。
public class RepositorySpliterator<T> implements Spliterator<T> {
private Slice<T> slice;
private Function<Pageable, Slice<T>> getSlice;
private Iterator<T> sliceIterator;
public RepositorySpliterator(Pageable pageable, Function<Pageable, Slice<T>> getSlice) {
this.getSlice = getSlice;
this.slice = this.getSlice.apply(pageable);
this.sliceIterator = slice.iterator();
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
if(sliceIterator.hasNext()) {
action.accept(sliceIterator.next());
return true;
} else if (slice.hasNext()) {
this.slice = getSlice.apply(slice.nextPageable());
this.sliceIterator = this.slice.iterator();
if(sliceIterator.hasNext()){
action.accept(sliceIterator.next());
return true;
}
}
return false;
}
public Stream<T> getStream(boolean parallel){
return StreamSupport.stream(this, parallel);
}
}
正如你所看到的,我输入了一个帮助函数来生成我需要的Stream。也许这有点草率但是......嗯。
所以现在我只需要在我的映射类中使用几行代码来实现目标。
public void start(Timestamp startTimestamp, Timestamp endTimestamp) {
new RepositorySpliterator<>(
new PageRequest(0, 10000), pageable -> sourceRepository.findAllBetween(startTimestamp, endTimestamp, pageable))
.getStream(true)
.map(entity -> mapToTarget(endTimestamp, entity))
.collect(new RepositoryCollector<>(targetRepository, 1000));
}
映射器将从源中获取10000个实体,将它们倒入流池中,以便映射和存储它们。每当一个流用完新实体时,将获取一个新的批次并将其送入同一个流池。
如果我的实施中存在明显的错误,请随时评论和改进!