2012-01-13 19 views
15

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> 
+0

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? –

+1

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

+0

đề xuất tuyệt vời! –

Trả lời

11

Tìm thấy câu trả lời cho tất cả những câu hỏi bản thân mình:

@Iterable: yes, iterable có thể được sử dụng cho readonly

@load on access: theo mặc định không có gì được tải. và tự động tải lười biếng không có sẵn (ít nhất là như xa như tôi có thể thu thập)

Đối với phần còn lại: Khi tôi cần một mối quan hệ tôi hoặc là phải sử dụng @Fetch hoặc sử dụng các phương pháp neo4jtemplate.fetch:

@NodeEntity 
public class User { 
    @GraphId Long nodeId; 
    @RelatedTo(type="user", direction = Direction.INCOMING) 
    private Iterable<Worker> worker; 
    @Fetch Unit currentUnit; 

    String name; 

} 

class GetService { 
    @Autowired private Neo4jTemplate template; 

    public void doSomethingFunction() { 
    User u = ....; 
    // worker is not avaiable here 

    template.fetch(u.worker); 
    // do something with the worker 
    } 
} 
+3

Có vẻ như thật lạ với tôi khi tải xuống tự động không có sẵn. Không nên có một cách để thực hiện nó mà không liên tục gọi template.fetch trong mã của bạn? – CorayThan

3

Không minh bạch, nhưng vẫn là lazy fetching.

template.fetch(person.getDirectReports()); 

Và @Fetch thực hiện tìm nạp mong muốn như đã được nêu trong câu trả lời của bạn.

+0

@ s-t-e-v-e có cách nào để phân trang không? như tôi đang cố gắng tìm 'Đặt ' có thể có số lượng lớn. – agpt

+0

@agpt SDN hỗ trợ Có thể thu gọn. Tôi đã không thử nó và tôi không rõ ràng như thế nào/nếu nó có ý nghĩa trong bối cảnh lười lấy. Xem http://stackoverflow.com/questions/20564661/spring-data-neo4j-pageable –

0

Tôi thích phương pháp tiếp cận khía cạnh để làm việc xung quanh giới hạn của cách dữ liệu mùa xuân hiện tại để xử lý tải chậm.

@niko - Tôi đã đưa mẫu mã của bạn trong một dự án maven cơ bản và cố gắng để có được giải pháp mà làm việc với rất ít thành công:

https://github.com/samuel-kerrien/neo4j-aspect-auto-fetching 

Đối với một số lý do Aspect được initialising nhưng những lời khuyên không dường như bị xử tử. Để tái tạo sự cố, chỉ cần chạy kiểm tra JUnit sau:

playground.neo4j.domain.UserTest 
+0

Xin chào Samuel, bạn đang thiếu aop.xml để báo cho apsectj biết nên áp dụng khía cạnh nào.cấu hình mùa xuân của bạn có vẻ bị thiếu ... – Niko

+0

@niko Tôi đã định cấu hình Spring thông qua các chú thích trong https://github.com/samuel-kerrien/neo4j-aspect-auto-fetching/blob/master/src/main/java /playground/neo4j/config/Neo4jConfig.java Tôi có thể thiếu một cái gì đó thiết yếu ... –

+0

bạn không thể thay thế aop.xml bằng lò xo (trong trường hợp đó) - chúng hoạt động riêng biệt với nhau: đó cũng là lý do tại sao khía cạnh trên các lớp MODEL và không phải là các daos neo4j. Tôi đã mô tả toàn bộ điều chi tiết hơn một chút trong http://stackoverflow.com/a/37808762/150740 – Niko