Tôi có một thiết lập đơn giản và gặp phải một khó hiểu (ít nhất là đối với tôi) vấn đề:Lazy/Háo hức tải/lấy trong Neo4j/mùa xuân-Data
tôi có ba POJO có liên quan đến nhau:
@NodeEntity
public class Unit {
@GraphId Long nodeId;
@Indexed int type;
String description;
}
@NodeEntity
public class User {
@GraphId Long nodeId;
@RelatedTo(type="user", direction = Direction.INCOMING)
@Fetch private Iterable<Worker> worker;
@Fetch Unit currentUnit;
String name;
}
@NodeEntity
public class Worker {
@GraphId Long nodeId;
@Fetch User user;
@Fetch Unit unit;
String description;
}
Vì vậy, bạn có Đơn vị công nhân người dùng với "currentunit" đánh dấu người dùng cho phép chuyển trực tiếp đến "đơn vị hiện tại". Mỗi người dùng có thể có nhiều công nhân, nhưng một công nhân chỉ được gán cho một đơn vị (một đơn vị có thể có nhiều công nhân).
Điều tôi thắc mắc là cách kiểm soát chú thích @Fetch trên "User.worker". Tôi thực sự muốn điều này được mã hóa chỉ khi cần thiết, bởi vì hầu hết thời gian tôi chỉ làm việc với "Công nhân".
tôi đã đi qua http://static.springsource.org/spring-data/data-neo4j/docs/2.0.0.RELEASE/reference/html/ và nó không phải là thực sự rõ ràng với tôi:
- nhân là iterable vì nó cần được đọc chỉ (liên quan đến) - trong tài liệu này được nêu clarly, nhưng trong các ví dụ '' Đặt '' được sử dụng hầu hết thời gian. Tại sao? hoặc không quan trọng ...
- Làm cách nào để nhân viên chỉ tải khi truy cập? (tải chậm)
- Tại sao tôi cần phải chú thích ngay cả các quan hệ đơn giản (worker.unit) với @Fetch. Có cách nào tốt hơn không? Tôi có một thực thể với MANY quan hệ đơn giản như vậy - Tôi thực sự muốn tránh phải tải toàn bộ đồ thị chỉ vì tôi muốn các thuộc tính của một đối tượng.
- Tôi có thiếu cấu hình mùa xuân để nó hoạt động với tải chậm không?
- Có cách nào để tải bất kỳ mối quan hệ nào (không được đánh dấu là @Fetch) thông qua cuộc gọi thêm không?
Từ cách tôi nhìn thấy, cấu trúc này tải toàn bộ cơ sở dữ liệu ngay khi tôi muốn một Người làm việc, ngay cả khi tôi không quan tâm đến Người dùng hầu hết thời gian.
Cách giải quyết duy nhất tôi thấy là sử dụng kho lưu trữ và tải thủ công các đối tượng khi cần.
------- Cập nhật -------
Tôi đã làm việc với neo4j một thời gian khá bây giờ và tìm ra giải pháp cho vấn đề trên mà không yêu cầu gọi lấy tất cả thời gian (và do đó không tải toàn bộ đồ thị). Nhược điểm duy nhất: đó là một khía cạnh thời gian chạy:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import my.modelUtils.BaseObject;
@Aspect
public class Neo4jFetchAspect {
// thew neo4j template - make sure to fill it
@Autowired private Neo4jTemplate template;
@Around("modelGetter()")
public Object autoFetch(ProceedingJoinPoint pjp) throws Throwable {
Object o = pjp.proceed();
if(o != null) {
if(o.getClass().isAnnotationPresent(NodeEntity.class)) {
if(o instanceof BaseObject<?>) {
BaseObject<?> bo = (BaseObject<?>)o;
if(bo.getId() != null && !bo.isFetched()) {
return template.fetch(o);
}
return o;
}
try {
return template.fetch(o);
} catch(MappingException me) {
me.printStackTrace();
}
}
}
return o;
}
@Pointcut("execution(public my.model.package.*.get*())")
public void modelGetter() {}
}
Bạn chỉ cần điều chỉnh đường dẫn lớp mà trên đó khía cạnh sẽ được áp dụng: my.model.package. .get()) ")
Tôi áp dụng khía cạnh cho TẤT CẢ các phương thức có được trên các lớp mô hình của tôi.Điều này đòi hỏi một vài prerequesites:
- Bạn PHẢI sử dụng thu khí trong lớp học mô hình của bạn (các khía cạnh không hoạt động trên các thuộc tính công cộng - mà bạn không nên sử dụng anyways)
- tất cả các lớp mô hình là trong cùng một gói (vì vậy bạn cần phải sửa lại mã một chút) - tôi đoán bạn có thể thích ứng bộ lọc
- AspectJ như một thành phần runtime được yêu cầu (một chút khó khăn khi bạn sử dụng tomcat) - nhưng nó hoạt động :)
TẤT CẢ các lớp mô hình phải triển khai giao diện BaseObject cung cấp:
Giao diện công cộng BaseObject { boolean công khai isFetched(); }
Điều này ngăn chặn tìm nạp kép. Tôi chỉ kiểm tra một phân lớp hoặc thuộc tính bắt buộc (tức là tên hoặc một cái gì đó khác ngoại trừ nodeId) để xem nó có thực sự được tìm nạp hay không. Neo4j sẽ tạo một đối tượng nhưng chỉ điền vào nodeId và để mọi thứ khác không bị ảnh hưởng (vì vậy mọi thứ khác là NULL).
ví dụ:
@NodeEntity
public class User implements BaseObject{
@GraphId
private Long nodeId;
String username = null;
@Override
public boolean isFetched() {
return username != null;
}
}
Nếu ai đó tìm thấy một cách để làm điều này mà không có cách giải quyết khác lạ xin vui lòng thêm giải pháp của bạn :) vì một này làm việc, nhưng tôi sẽ yêu thương mà không AspectJ.
thiết kế đối tượng cơ sở đó doenst đòi hỏi một tấm séc lĩnh vực tùy chỉnh
Một tối ưu hóa sẽ tạo ra một cơ sở lớp thay vì một giao diện mà thực sự sử dụng một lĩnh vực Boolean (Boolean nạp) và kiểm tra trên đó (vì vậy bạn không cần phải lo lắng về việc kiểm tra thủ công)
public abstract class BaseObject {
private Boolean loaded;
public boolean isFetched() {
return loaded != null;
}
/**
* getLoaded will always return true (is read when saving the object)
*/
public Boolean getLoaded() {
return true;
}
/**
* setLoaded is called when loading from neo4j
*/
public void setLoaded(Boolean val) {
this.loaded = val;
}
}
Điều này hoạt động vì khi lưu đối tượng "true" được trả lại để tải. Khi khía cạnh nhìn vào đối tượng, nó sử dụng isFetched() - khi đối tượng chưa được truy xuất sẽ trả về null. Một khi đối tượng được lấy ra, setLoaded được gọi và biến được nạp được đặt thành true.
Làm thế nào để ngăn chặn jackson kích hoạt tải chậm?
(Là câu trả lời cho câu hỏi trong nhận xét - lưu ý rằng tôi chưa dùng thử vì tôi chưa có sự cố này).
Với jackson, tôi đề xuất sử dụng bộ nối tiếp tùy chỉnh (xem ví dụ: http://www.baeldung.com/jackson-custom-serialization). Điều này cho phép bạn kiểm tra thực thể trước khi nhận các giá trị.Bạn chỉ cần làm một kiểm tra nếu nó đã được lấy và một trong hai đi về với toàn bộ serialization hoặc chỉ sử dụng id:
public class ItemSerializer extends JsonSerializer<BaseObject> {
@Override
public void serialize(BaseObject value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
// serialize the whole object
if(value.isFetched()) {
super.serialize(value, jgen, provider);
return;
}
// only serialize the id
jgen.writeStartObject();
jgen.writeNumberField("id", value.nodeId);
jgen.writeEndObject();
}
}
Xuân Cấu hình
Đây là một cấu hình mẫu Xuân Tôi sử dụng - bạn cần để điều chỉnh các gói để dự án của bạn:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:annotation-config/>
<context:spring-configured/>
<neo4j:repositories base-package="my.dao"/> <!-- repositories = dao -->
<context:component-scan base-package="my.controller">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <!-- that would be our services -->
</context:component-scan>
<tx:annotation-driven mode="aspectj" transaction-manager="neo4jTransactionManager"/>
<bean class="corinis.util.aspects.Neo4jFetchAspect" factory-method="aspectOf"/>
</beans>
AOP cấu hình
đây là /META-INF/aop.xml để làm việc này:
<!DOCTYPE aspectj PUBLIC
"-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages -->
<include within="my.model.*" />
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="my.util.aspects.Neo4jFetchAspect" />
</aspects>
</aspectj>
Giải pháp tìm nạp tự động tốt! Một vấn đề mà tôi có với điều này là chúng tôi sử dụng một số khung công tác như Jackson mà tôi không muốn cho phép tìm nạp tự động. Tôi có thể tất nhiên để lại các getters để Jackson và thực hiện này cắt trên *. LazyGet *() phương pháp, nhưng đó sẽ gần như là giống như viết neo4jTemplate.fetch (*. Get *()) (thực sự chúng tôi đã viết wrapper của riêng của chúng tôi ngăn cản tìm nạp kép, vì vậy hiệu ứng sẽ giống nhau). Làm thế nào bạn sẽ giải quyết câu hỏi hóc búa này? –
Tôi đã thêm một giải pháp có thể có trong văn bản câu hỏi - có thể chỉnh sửa nó bằng đoạn mã hoạt động vì tôi không kiểm tra điều này. – Niko
đề xuất tuyệt vời! –