2014-05-22 7 views
7

मैं एक PageAndSortingRepository पर एक RepositoryRestResource कॉन्फ़िगर किया है कि एक इकाई है कि एक समग्र आईडी भी शामिल पहुँचता:अनुकूलित HATEOAS लिंक पीढ़ी

@Entity 
@IdClass(CustomerId.class) 
public class Customer { 
    @Id BigInteger id; 
    @Id int startVersion; 
    ... 
} 

public class CustomerId { 
    BigInteger id; 
    int startVersion; 
    ... 
} 

@RepositoryRestResource(collectionResourceRel = "customers", path = "customers", itemResourceRel = "customers/{id}_{startVersion}") 
public interface CustomerRepository extends PagingAndSortingRepository<Customer, CustomerId> {} 

जब मैं उदाहरण के लिए "http://<server>/api/customers/1_1" पर सर्वर का उपयोग, मैं "http://<server>/api/customer/1"

यानी:

{ 
    "id" : 1, 
    "startVersion" : 1, 
    ... 
    "firstname" : "BOB", 
    "_links" : { 
    "self" : { 
     "href" : "http://localhost:9081/reps/api/reps/1" <-- This should be /1_1 
    } 
    } 
} 
सही संसाधन वापस json के रूप में है, लेकिन स्वयं के लिए _links खंड में href गलत है और यह भी किसी अन्य ग्राहक मैं क्वेरी के लिए एक ही है

मुझे लगता है कि यह मेरे समग्र आईडी की वजह से है, लेकिन मुझे इस बारे में बताया गया है कि मैं इस डिफ़ॉल्ट व्यवहार को कैसे बदल सकता हूं।

मैंने ResourceSupport और ResourceProcessor कक्षा पर एक नज़र डाली है लेकिन मुझे यकीन नहीं है कि इस समस्या को ठीक करने के लिए मुझे कितना बदलाव करना होगा।

क्या कोई जो वसंत जानता है वह मुझे हाथ दे सकता है?

उत्तर

10

दुर्भाग्यवश, 2.1.0.RELEASE तक सभी स्प्रिंग डेटा जेपीए/रेस्ट संस्करण बॉक्स से आपकी आवश्यकता को पूरा करने में सक्षम नहीं हैं। स्रोत को स्प्रिंग डेटा कॉमन्स/जेपीए के अंदर ही दफनाया गया है। स्प्रिंग डेटा जेपीए पहचानकर्ता के रूप में केवल Id और EmbeddedId का समर्थन करता है।

अंश JpaPersistentPropertyImpl:

static { 

    // [...] 

    annotations = new HashSet<Class<? extends Annotation>>(); 
    annotations.add(Id.class); 
    annotations.add(EmbeddedId.class); 

    ID_ANNOTATIONS = annotations; 
} 

स्प्रिंग डाटा कॉमन्स संयुक्त गुण की धारणा का समर्थन नहीं करता। यह एक दूसरे से स्वतंत्र रूप से कक्षा की हर संपत्ति का इलाज करता है।

बेशक, आप स्प्रिंग डेटा रेस्ट को हैक कर सकते हैं। लेकिन यह बोझिल है, समस्या को हल नहीं करता है और ढांचे की लचीलापन को कम करता है।

यहां हैक है। यह आपको एक समस्या दे सकता है कि आपकी समस्या से निपटने के लिए कैसे।

आपकी कॉन्फ़िगरेशन में repositoryExporterHandlerAdapter ओवरराइड करें और CustomPersistentEntityResourceAssemblerArgumentResolver लौटाएं। साथ ही, backendIdConverterRegistry ओवरराइड और ज्ञात id converter की सूची में CustomBackendIdConverter जोड़ें:

import org.springframework.beans.factory.ListableBeanFactory; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.context.annotation.Import; 
import org.springframework.data.rest.core.projection.ProxyProjectionFactory; 
import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter; 
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration; 
import org.springframework.data.rest.webmvc.spi.BackendIdConverter; 
import org.springframework.data.rest.webmvc.support.HttpMethodHandlerMethodArgumentResolver; 
import org.springframework.data.web.config.EnableSpringDataWebSupport; 
import org.springframework.hateoas.ResourceProcessor; 
import org.springframework.http.converter.HttpMessageConverter; 
import org.springframework.plugin.core.OrderAwarePluginRegistry; 
import org.springframework.plugin.core.PluginRegistry; 
import org.springframework.web.method.support.HandlerMethodArgumentResolver; 
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; 

import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.Collections; 
import java.util.List; 

@Configuration 
@Import(RepositoryRestMvcConfiguration.class) 
@EnableSpringDataWebSupport 
public class RestConfig extends RepositoryRestMvcConfiguration { 
    @Autowired(required = false) List<ResourceProcessor<?>> resourceProcessors = Collections.emptyList(); 
    @Autowired 
    ListableBeanFactory beanFactory; 

    @Override 
    @Bean 
    public PluginRegistry<BackendIdConverter, Class<?>> backendIdConverterRegistry() { 

     List<BackendIdConverter> converters = new ArrayList<BackendIdConverter>(3); 
     converters.add(new CustomBackendIdConverter()); 
     converters.add(BackendIdConverter.DefaultIdConverter.INSTANCE); 

     return OrderAwarePluginRegistry.create(converters); 
    } 

    @Bean 
    public RequestMappingHandlerAdapter repositoryExporterHandlerAdapter() { 

     List<HttpMessageConverter<?>> messageConverters = defaultMessageConverters(); 
     configureHttpMessageConverters(messageConverters); 

     RepositoryRestHandlerAdapter handlerAdapter = new RepositoryRestHandlerAdapter(defaultMethodArgumentResolvers(), 
       resourceProcessors); 
     handlerAdapter.setMessageConverters(messageConverters); 

     return handlerAdapter; 
    } 

    private List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers() 
    { 

     CustomPersistentEntityResourceAssemblerArgumentResolver peraResolver = new CustomPersistentEntityResourceAssemblerArgumentResolver(
       repositories(), entityLinks(), config().projectionConfiguration(), new ProxyProjectionFactory(beanFactory)); 

     return Arrays.asList(pageableResolver(), sortResolver(), serverHttpRequestMethodArgumentResolver(), 
       repoRequestArgumentResolver(), persistentEntityArgumentResolver(), 
       resourceMetadataHandlerMethodArgumentResolver(), HttpMethodHandlerMethodArgumentResolver.INSTANCE, 
       peraResolver, backendIdHandlerMethodArgumentResolver()); 
    } 
} 

CustomBackendIdConverter बनाएँ।यह वर्ग अपने कस्टम इकाई आईडी प्रतिपादन के लिए जिम्मेदार है:

import org.springframework.data.rest.webmvc.spi.BackendIdConverter; 

import java.io.Serializable; 

public class CustomBackendIdConverter implements BackendIdConverter { 

    @Override 
    public Serializable fromRequestId(String id, Class<?> entityType) { 
     return id; 
    } 

    @Override 
    public String toRequestId(Serializable id, Class<?> entityType) { 
     if(entityType.equals(Customer.class)) { 
      Customer c = (Customer) id; 
      return c.getId() + "_" +c.getStartVersion(); 
     } 
     return id.toString(); 

    } 

    @Override 
    public boolean supports(Class<?> delimiter) { 
     return true; 
    } 
} 

CustomPersistentEntityResourceAssemblerArgumentResolver बारी में एक CustomPersistentEntityResourceAssembler लौटना चाहिए:

import org.springframework.core.MethodParameter; 
import org.springframework.data.repository.support.Repositories; 
import org.springframework.data.rest.core.projection.ProjectionDefinitions; 
import org.springframework.data.rest.core.projection.ProjectionFactory; 
import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler; 
import org.springframework.data.rest.webmvc.config.PersistentEntityResourceAssemblerArgumentResolver; 
import org.springframework.data.rest.webmvc.support.PersistentEntityProjector; 
import org.springframework.hateoas.EntityLinks; 
import org.springframework.web.bind.support.WebDataBinderFactory; 
import org.springframework.web.context.request.NativeWebRequest; 
import org.springframework.web.method.support.ModelAndViewContainer; 

public class CustomPersistentEntityResourceAssemblerArgumentResolver extends PersistentEntityResourceAssemblerArgumentResolver { 
    private final Repositories repositories; 
    private final EntityLinks entityLinks; 
    private final ProjectionDefinitions projectionDefinitions; 
    private final ProjectionFactory projectionFactory; 

    public CustomPersistentEntityResourceAssemblerArgumentResolver(Repositories repositories, EntityLinks entityLinks, 
                  ProjectionDefinitions projectionDefinitions, ProjectionFactory projectionFactory) { 

     super(repositories, entityLinks,projectionDefinitions,projectionFactory); 

     this.repositories = repositories; 
     this.entityLinks = entityLinks; 
     this.projectionDefinitions = projectionDefinitions; 
     this.projectionFactory = projectionFactory; 
    } 

    public boolean supportsParameter(MethodParameter parameter) { 
     return PersistentEntityResourceAssembler.class.isAssignableFrom(parameter.getParameterType()); 
    } 

    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, 
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { 

     String projectionParameter = webRequest.getParameter(projectionDefinitions.getParameterName()); 
     PersistentEntityProjector projector = new PersistentEntityProjector(projectionDefinitions, projectionFactory, 
       projectionParameter); 

     return new CustomPersistentEntityResourceAssembler(repositories, entityLinks, projector); 
    } 
} 

CustomPersistentEntityResourceAssembler जरूरतों getSelfLinkFor ओवरराइड करने के लिए। जैसा कि आप देख सकते हैं entity.getIdProperty() या तो Customer कक्षा की आईडी या स्टार्टवर्सन प्रॉपर्टी लौटाएं जो बदले में BeanWrapper की सहायता से वास्तविक मूल्य को पुनर्प्राप्त करने के लिए उपयोग की जाती है। यहां हम instanceof ऑपरेटर के उपयोग के साथ पूरे ढांचे को शॉर्ट सर्किट कर रहे हैं। इसलिए आगे की प्रक्रिया के लिए Customer वर्ग को Serializable लागू करना चाहिए।

import org.springframework.data.mapping.PersistentEntity; 
import org.springframework.data.mapping.model.BeanWrapper; 
import org.springframework.data.repository.support.Repositories; 
import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler; 
import org.springframework.data.rest.webmvc.support.Projector; 
import org.springframework.hateoas.EntityLinks; 
import org.springframework.hateoas.Link; 
import org.springframework.util.Assert; 

public class CustomPersistentEntityResourceAssembler extends PersistentEntityResourceAssembler { 

    private final Repositories repositories; 
    private final EntityLinks entityLinks; 

    public CustomPersistentEntityResourceAssembler(Repositories repositories, EntityLinks entityLinks, Projector projector) { 
     super(repositories, entityLinks, projector); 

     this.repositories = repositories; 
     this.entityLinks = entityLinks; 
    } 

    public Link getSelfLinkFor(Object instance) { 

     Assert.notNull(instance, "Domain object must not be null!"); 

     Class<? extends Object> instanceType = instance.getClass(); 
     PersistentEntity<?, ?> entity = repositories.getPersistentEntity(instanceType); 

     if (entity == null) { 
      throw new IllegalArgumentException(String.format("Cannot create self link for %s! No persistent entity found!", 
        instanceType)); 
     } 

     Object id; 

     //this is a hack for demonstration purpose. don't do this at home! 
     if(instance instanceof Customer) { 
      id = instance; 
     } else { 
      BeanWrapper<Object> wrapper = BeanWrapper.create(instance, null); 
      id = wrapper.getProperty(entity.getIdProperty()); 
     } 

     Link resourceLink = entityLinks.linkToSingleResource(entity.getType(), id); 
     return new Link(resourceLink.getHref(), Link.REL_SELF); 
    } 
} 

यही है! ,

{ 
    "_embedded" : { 
    "customers" : [ { 
     "name" : "test", 
     "_links" : { 
     "self" : { 
      "href" : "http://localhost:8080/demo/customers/1_1" 
     } 
     } 
    } ] 
    } 
} 

IMHO अगर आप एक ग्रीन फील्ड परियोजना पर काम कर रहे हैं मैं पूरी तरह IdClass खाई और तकनीकी सरल लांग वर्ग के आधार पर आईडी के साथ जाने के लिए सुझाव है: आप इस यूआरआई देखना चाहिए। इसका परीक्षण स्प्रिंग डेटा रेस्ट 2.1.0.RELEASE, स्प्रिंग डेटा जेपीए 1.6.0.RELEASE और स्प्रिंग फ्रेमवर्क 4.0.3.RELEASE के साथ किया गया था।

+0

जाने का सबसे अच्छा तरीका कोई समस्या नहीं है। यह एक दिलचस्प समस्या है। आशा करता हूँ की ये काम करेगा। हो सकता है कि आपको अपनी आईडी को deserialize करने के लिए 'सेRequestId' को लागू करने की आवश्यकता हो। –

+0

अच्छा जवाब! मैंने अभी भी कोशिश नहीं की है, लेकिन यह भंडार POST अनुरोध स्वीकार करेगा? मैं आरईएसटी एपीआई के माध्यम से डेटा कैसे डाल सकता हूं? –

4

हालांकि वांछनीय नहीं है, मैंने अपनी जेपीए इकाई पर IdClass एनोटेशन के बजाय @EmbeddedId का उपयोग करके इस समस्या के आसपास काम किया है।

तो जैसा

:

@Entity 
public class Customer { 
    @EmbeddedId 
    private CustomerId id; 
    ... 
} 

public class CustomerId { 

    @Column(...) 
    BigInteger key; 
    @Column(...) 
    int startVersion; 
    ... 
} 

अब मैं अपने लौटे संस्थाओं पर सही ढंग से जेनरेट किए गए लिंक 1_1 देखते हैं।

यदि कोई मुझे अभी भी ऐसे समाधान पर निर्देशित कर सकता है जिसकी आवश्यकता नहीं है तो मैं अपने मॉडल का प्रतिनिधित्व बदलता हूं, इसकी अत्यधिक सराहना की जाएगी। सौभाग्य से मैंने बदलने के लिए गंभीर चिंता होने के लिए अपने आवेदन विकास में बहुत प्रगति नहीं की थी, लेकिन मुझे लगता है कि दूसरों के लिए, इस तरह के बदलाव करने में महत्वपूर्ण ओवरहेड होगा: (उदाहरण के लिए जेपीक्यूएल में इस मॉडल को संदर्भित करने वाले सभी प्रश्नों को बदलना क्वेरी)।

+2

मुझे पता है कि यह एक पुरानी पोस्ट है, लेकिन मैंने सोचा कि मैं जोड़ूंगा कि आप 'getKey' विधि में' @ क्षणिक 'जोड़ सकते हैं जो 'id.key' को' ग्राहक' में वापस कर देगा जो आपको अनुमति देगा अपने एपीआई में त्वरित पहुंच है, लेकिन आपके आरईएसटी प्रतिनिधित्व को प्रभावित नहीं करेगा। यह बदसूरत है, लेकिन आपके एपीआई क्लीनर बनाता है। – Andy

+0

आईएमएचओ – Sam

0

मुझे ऐसी ही समस्या थी जहां डेटा आराम के लिए समग्र कुंजी परिदृश्य काम नहीं कर रहे थे। @ksokol विस्तृत स्पष्टीकरण ने इस मुद्दे को हल करने के लिए आवश्यक इनपुट प्रदान किए। मुख्य उद्देश्य डेटा आराम-webmvc और डेटा जेपीए के लिए मेरे पोम बदल

<dependency> 
     <groupId>org.springframework.data</groupId> 
     <artifactId>spring-data-rest-webmvc</artifactId> 
     <version>2.2.1.RELEASE</version> 
    </dependency> 

    <dependency> 
     <groupId>org.springframework.data</groupId> 
     <artifactId>spring-data-jpa</artifactId> 
     <version>1.7.1.RELEASE</version> 
    </dependency> 

जो समग्र कुंजी से संबंधित सभी मुद्दों का हल और मैं अनुकूलन करने की जरूरत नहीं है। विस्तृत स्पष्टीकरण के लिए धन्यवाद ksokol।

+0

हैलो @ अलासेसन, क्या आपको पता है कि स्प्रिंग डेटा जेपीए या आरईएसटी संस्करण कौन सा समस्या हल करता है? धन्यवाद – bluish

0

सबसे पहले, वसंत से बीन प्राप्त करने के लिए एक SpringUtil बनाएं।

import org.springframework.beans.BeansException; 
import org.springframework.context.ApplicationContext; 
import org.springframework.context.ApplicationContextAware; 
import org.springframework.stereotype.Component; 

@Component 
public class SpringUtil implements ApplicationContextAware { 
    private static ApplicationContext applicationContext; 

    @Override 
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 
     if(SpringUtil.applicationContext == null) { 
      SpringUtil.applicationContext = applicationContext; 
     } 
    } 

    public static ApplicationContext getApplicationContext() { 
     return applicationContext; 
    } 

    public static Object getBean(String name){ 
     return getApplicationContext().getBean(name); 
    } 

    public static <T> T getBean(Class<T> clazz){ 
     return getApplicationContext().getBean(clazz); 
    } 

    public static <T> T getBean(String name,Class<T> clazz){ 
     return getApplicationContext().getBean(name, clazz); 
    } 
} 

फिर, बैकएंडआईड कनवर्टर को कार्यान्वित करें।

import com.alibaba.fastjson.JSON; 
import com.example.SpringUtil; 
import org.springframework.data.jpa.repository.JpaRepository; 
import org.springframework.data.rest.webmvc.spi.BackendIdConverter; 
import org.springframework.stereotype.Component; 

import javax.persistence.EmbeddedId; 
import javax.persistence.Id; 
import java.io.Serializable; 
import java.io.UnsupportedEncodingException; 
import java.lang.reflect.Method; 
import java.net.URLDecoder; 
import java.net.URLEncoder; 

@Component 
public class CustomBackendIdConverter implements BackendIdConverter { 

    @Override 
    public boolean supports(Class<?> delimiter) { 
     return true; 
    } 

    @Override 
    public Serializable fromRequestId(String id, Class<?> entityType) { 
     if (id == null) { 
      return null; 
     } 

     //first decode url string 
     if (!id.contains(" ") && id.toUpperCase().contains("%7B")) { 
      try { 
       id = URLDecoder.decode(id, "UTF-8"); 
      } catch (UnsupportedEncodingException e) { 
       e.printStackTrace(); 
      } 
     } 

     //deserialize json string to ID object 
     Object idObject = null; 
     for (Method method : entityType.getDeclaredMethods()) { 
      if (method.isAnnotationPresent(Id.class) || method.isAnnotationPresent(EmbeddedId.class)) { 
       idObject = JSON.parseObject(id, method.getGenericReturnType()); 
       break; 
      } 
     } 

     //get dao class from spring 
     Object daoClass = null; 
     try { 
      daoClass = SpringUtil.getBean(Class.forName("com.example.db.dao." + entityType.getSimpleName() + "DAO")); 
     } catch (ClassNotFoundException e) { 
      e.printStackTrace(); 
     } 

     //get the entity with given primary key 
     JpaRepository simpleJpaRepository = (JpaRepository) daoClass; 
     Object entity = simpleJpaRepository.findOne((Serializable) idObject); 
     return (Serializable) entity; 

    } 

    @Override 
    public String toRequestId(Serializable id, Class<?> entityType) { 
     if (id == null) { 
      return null; 
     } 

     String jsonString = JSON.toJSONString(id); 

     String encodedString = ""; 
     try { 
      encodedString = URLEncoder.encode(jsonString, "UTF-8"); 
     } catch (UnsupportedEncodingException e) { 
      e.printStackTrace(); 
     } 
     return encodedString; 
    } 
} 

उसके बाद। आप जो चाहते है वो कर सकते हैं।

नीचे एक नमूना है।

  • यदि इकाई में एकल संपत्ति पीके है, तो आप लोकलहोस्ट का उपयोग कर सकते हैं: 8080/डेमो/1 सामान्य के रूप में। मेरे कोड के अनुसार, मान लें कि पीके में एनोटेशन "@Id" है।
  • इकाई pk बना दिया है, तो लगता है कि पी demoId प्रकार है, और एनोटेशन "@EmbeddedId" है, तो आप स्थानीय होस्ट का उपयोग कर सकते हैं: 8080/डेमो/{ json demoId}// पुट मिल नष्ट करने के लिए। और आपका स्वयं का लिंक वही होगा।
संबंधित मुद्दे