Spring MVC 3:将Spring- data页面作为JSON问题返回给PagedResourcesAssembler。

时间:2022-09-11 16:31:17

So, I'm fairly new to spring and to java in general

所以,我对spring和java都很熟悉。

What I try to do is to have on the same rendered view the form to post data to filter the result list displayed under the form.

我所要做的是在相同的呈现视图中,以post数据来过滤表单下显示的结果列表。

I have a simple domain class as follows:

我有一个简单的域类:

@Entity
@Table(name = "SEC_PERSON")
public class Person {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_SEC_PERSON")
@SequenceGenerator(name = "SEQ_SEC_PERSON", sequenceName = "SEQ_SEC_PERSON")
@Column(name = "ID")
private Long Id;

@Column(name = "CODE", nullable = false)
private String code;

@Column(name = "FIRSTNAME", nullable = false)
private String firstname;

@Column(name = "SURNAME", nullable = false)
private String surname;

@Column(name = "CREATIONDATE")
private DateTime creationDate;

//getters and setters

a DTO because I want my domain to be decoupled from my presentation

一个DTO因为我希望我的域与我的表示解耦。

public class PersonDTO {
private Long id;

@NotEmpty
private String code;

@NotEmpty
private String firstname;

@NotEmpty
private String surname;

private DateTime creationDate;

public PersonDTO() {
}
//getters and setters

a repository extending Jpa and QueryDsl

扩展Jpa和QueryDsl的存储库。

public interface PersonRepository extends JpaRepository<Person, Long>, QueryDslPredicateExecutor<Person> {}

a data access class for my search that is null safe (thanks guava) and its equivalent not null safe

我搜索的一个数据访问类是null safe(谢谢guava),它的值也不是null。

The person Criteria:

人标准:

public class PersonCriteria {

private String code;
private String surname;
private String firstname;
private LocalDate creationDateFrom;
private LocalDate creationDateTo;

//getters and setters
}

The null safe version

零安全版本

public class NullSafePersonCriteria {

private final PersonCriteria personCriteria;

public NullSafePersonCriteria(final PersonCriteria personCriteria) {
    checkArgument(personCriteria != null);
    this.personCriteria = personCriteria;
}

public Optional<String> getCode() {
    return Optional.fromNullable(this.personCriteria.getCode());
}

public Optional<String> getSurname() {
    return Optional.fromNullable(this.personCriteria.getSurname());
}

public Optional<String> getFirstname() {
    return Optional.fromNullable(this.personCriteria.getFirstname());
}

public Optional<LocalDate> getCreationDateFrom() {
    return Optional.fromNullable(this.personCriteria.getCreationDateFrom());
}

public Optional<LocalDate> getCreationDateTo() {
    return Optional.fromNullable(this.personCriteria.getCreationDateTo());
}

My predicate to search

我的谓词来搜索

public class PersonPredicates {
public static Predicate PersonLitstQuery(final PersonCriteria personCriteria) {
    final QPerson person = QPerson.person;
    final NullSafePersonCriteria nsPersonCriteria = new NullSafePersonCriteria(personCriteria);

    BooleanExpression criteria = QPerson.person.isNotNull();
    if (nsPersonCriteria.getCode().isPresent()) {
        criteria = criteria.and(person.code.matches(nsPersonCriteria.getCode().get()));
    }

    if (nsPersonCriteria.getSurname().isPresent()) {
        criteria.and(person.surname.startsWithIgnoreCase(nsPersonCriteria.getSurname().get()));
    }

    if (nsPersonCriteria.getFirstname().isPresent()) {
        criteria.and(person.firstname.startsWithIgnoreCase(nsPersonCriteria.getFirstname().get()));
    }

    if ((nsPersonCriteria.getCreationDateFrom().isPresent()) && (nsPersonCriteria.getCreationDateTo().isPresent())) {
        criteria.and(person.creationDate.between(nsPersonCriteria.getCreationDateFrom().get().toDateTimeAtStartOfDay(),
                nsPersonCriteria.getCreationDateTo().get().toDateTimeAtStartOfDay()));
    }

    return criteria;
}

My Service implementation is as follows:

我的服务实施情况如下:

@Service
public class PersonServiceImpl implements PersonService{
@Transactional(readOnly = true)
@Override
public Page<PersonDTO> search(final PersonCriteria criteria, final int pageIndex) {
    LOGGER.debug("Searching person with set of criterias");

    return new PersonPage(this.mapper.map(Lists.newArrayList(this.personRepository.findAll(PersonLitstQuery(criteria))),
            PersonDTO.class), constructPageSpecification(pageIndex), count(criteria));
}

The mapper that I use is just extending a bit DozerMapper:

我使用的映射器只是扩展了一点DozerMapper:

public class DozerMapper{
private final org.dozer.Mapper mapper;

public DozerMapper(final org.dozer.Mapper mapper) {
    this.mapper = mapper;
}

@Override
public <T> T map(final Object source, final Class<T> destinationClass) {
    return this.mapper.map(source, destinationClass);
}

@Override
public <T> List<T> map(final List<?> sources, final Class<T> destinationClass) {
    final List<T> result = Lists.newArrayList();
    for (final Object source : sources) {
        result.add(map(source, destinationClass));
    }
    return result;
}

Now, all of the above works, fine is unit tested and returns the results I want. My problem is with the controller and the views....

现在,以上所有的工作,fine是单元测试并返回我想要的结果。我的问题是与控制器和视图....

I have carefully read Oliver's answer to this question: Spring MVC 3: return a Spring-Data Page as JSON

我仔细阅读了奥利弗对这个问题的回答:Spring MVC 3:将Spring- data页面作为JSON返回。

though for some reason I cannot make it work. I've added the following dependencies to my project to use HATEOAS and Spring-data-commons:

尽管出于某种原因,我不能让它发挥作用。我在我的项目中添加了如下的依赖项,以使用HATEOAS和Spring-data-commons:

<dependency>
    <groupId>org.springframework.hateoas</groupId>
    <artifactId>spring-hateoas</artifactId>
    <version>0.7.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>1.6.0.RELEASE</version>
</dependency>

and my controller looks like this:

我的控制器是这样的

@Controller
@SessionAttributes("person")
public class PersonController

@RequestMapping(value = REQUEST_MAPPING_LIST, method = RequestMethod.GET)
public HttpEntity<PagedResources> persons(final Model model, @ModelAttribute final PersonCriteria searchCriteria,
        final Pageable pageable, final PagedResourcesAssembler assembler) {
    model.addAttribute(MODEL_ATTRIBUTE_SEARCHCRITERIA, searchCriteria);
    final Page<PersonDTO> persons = this.personService.search(searchCriteria, searchCriteria.getPageIndex());
    return new ResponseEntity<>(assembler.toResource(persons), HttpStatus.OK);
}

and my jsp:

和我的jsp:

<html>
<head>
    <title>testing</title>
    <script src="jslinks for jqGrid and jquery" type="text/javascript"></script>
</head>
<body>
<form:form action="person" commandName="searchCriteria" method="POST">
    <div>
        <form:label path="code">Code: </form:label>
        <form:input path="code" type="text"/>
        <form:label path="surname">Surname: </form:label>
        <form:input path="surname" type="text"/>
        <form:label path="firstname">Firstname: </form:label>
        <form:input path="firstname" type="text"/>
        <form:label path="creationDateFrom">Creation Date From: </form:label>
        <smj:datepicker id="creationDateFrom" name="CreationDateFrom" />
        <form:label path="creationDateTo">Creation Date To: </form:label>
        <smj:datepicker id="creationDateTo" name="CreationDateTo" />
    </div>
    <div>
        <input type="submit" value="search"/>
    </div>
</form:form>

    <smjg:grid 
        gridModel="gridModel" 
        id="persons" 
        datatype="\"json\""
        url="\'person\'"
        jsonReader="{root:\"content\", repeatitems: false, records: \"numberOfElements\", total: \"totalPages\"}">
        <smjg:gridColumn name="code" />
        <smjg:gridColumn name="surname" align="left"/>
        <smjg:gridColumn name="firstname" align="left"/>
    </smjg:grid>
</body>
</html>

Explanation: the smj and smjg tags are taglibs that I'm currently working on that are linking jquery to spring mvc. Ex: smjg:grid will create the tag and the javascript that will call the jqgrid function.

说明:smj和smjg标签是我目前正在开发的将jquery链接到spring mvc的标签。smjg:grid将创建标记和将调用jqgrid函数的javascript。

The first difference from Olivier's answer from this post Spring MVC 3: return a Spring-Data Page as JSON is that If I infer the PersonDTO within my HttpEntity then I get the following compilation error:

从这个Spring MVC 3中,Olivier的答案的第一个区别是:返回一个Spring- data页面作为JSON,如果我在我的HttpEntity中推断出PersonDTO,那么我就会得到以下编译错误:

Type mismatch: cannot convert from ResponseEntity<PagedResources> to HttpEntity<PagedResources<PersonDTO>>

the second difference is that it seems I should infer my PersonDTO into the PagedResourcesAssembler, is that correct?

第二个不同之处在于,似乎我应该推断出我的PersonDTO进入了PagedResourcesAssembler,对吗?

The outcome when I call the url directly localhost:8081/app/person I get a http 500 error:

当我直接调用localhost:8081/app/person时,结果是http 500错误:

org.springframework.http.converter.HttpMessageNotWritableException: Could not marshal [PagedResource { content: [Resource { content: com.app.admin.service.PersonDTO@60a349d0[id=2050,code=TEST2,firstname=ChadsdaTest,surname=Francois,creationDate=<null>], links: [] }, Resource { content: com.app.admin.service.PersonDTO@48462da5[id=5050,code=TESTNEW,firstname=Francois,surname=asdasdx,creationDate=<null>], links: [] }, Resource { content: com.app.admin.crediadmin.service.PersonDTO@5458c9fc[id=51,code=TEST,firstname=Francois,surname=asdawdsx,creationDate=<null>], links: [] }, Resource { content: com.app.admin.service.PersonDTO@de47c70[id=2051,code=TEST3,firstname=Chaqweqasdamsh,surname=Frasda,creationDate=<null>], links: [] }, Resource { content: com.app.admin.service.PersonDTO@7bd2085d[id=3053,code=TEST7,firstname=Francois,surname=Cadsdsx,creationDate=<null>], links: [] }, Resource { content: com.app.admin.service.PersonDTO@14676697[id=3050,code=TESTER,firstname=Francois,surname=CasdadsixChaix,creationDate=<null>], links: [] }, Resource { content: com.app.admin.service.PersonDTO@109de504[id=3051,code=TESTER3,firstname=FrancoisF,surname=Chtest,creationDate=<null>], links: [] }], metadata: Metadata { number: 0, total pages: 2, total elements: 7, size: 5 }, links: [<http://localhost:8081/app/person?page=1&size=5&sort=surname,asc>;rel="next"] }]: null; nested exception is javax.xml.bind.MarshalException
- with linked exception:
[com.sun.istack.SAXException2: unable to marshal type  "org.springframework.hateoas.Resource" as an element because it is not known to this context.]
org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter.writeToResult(Jaxb2RootElementHttpMessageConverter.java:99)
org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter.writeInternal(AbstractXmlHttpMessageConverter.java:66)
org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:179)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:148)
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:124)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:69)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:122)

and the root cause:

根本原因:

javax.xml.bind.MarshalException
- with linked exception: [com.sun.istack.SAXException2: unable to marshal type "org.springframework.hateoas.Resource" as an element because it is not known to this context.]
com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:318)
com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:244)

I'm not sure of what I do wrong here.

我不知道我在这里做错了什么。

Althoug if I call the same url with .json then I get the json output which seems weird as I don't produce the json still.

如果我用。json调用同一个url,我得到json输出,这看起来很奇怪,因为我还没有生成json。

1 个解决方案

#1


10  

You've probably solved this by now, but since I have this working I thought I'd add the solution to at least one of your problems for anyone else in a similar boat.

你现在可能已经解决了这个问题,但是既然我有了这个工作,我想我应该把这个解决方案至少添加到你的一个问题中。

Type mismatch: cannot convert from ResponseEntity<PagedResources> to HttpEntity<PagedResources<PersonDTO>>:

类型不匹配:不能将ResponseEntity 转换为HttpEntity >:

To solve this, add an additional type parameter to your return type:

要解决这个问题,请在返回类型中添加一个额外的类型参数:

@RequestMapping(value = REQUEST_MAPPING_LIST, method = RequestMethod.GET)
public HttpEntity<PagedResources<PersonDTO>> persons(final Model model, @ModelAttribute final PersonCriteria searchCriteria,
        final Pageable pageable, final PagedResourcesAssembler assembler) {
    ...
}

com.sun.istack.SAXException2: unable to marshal type "org.springframework.hateoas.Resource" as an element because it is not known to this context.]

com.sun.istack。SAXException2:无法编组类型“org.springframework.hateoas”。资源“作为一个元素,因为它不知道这个上下文。”

It looks like Spring is trying to produce XML, which I think it does by default if it finds a JAXB implementation on the classpath. If you don't need to produce XML from this method, you could add produces = {MediaType.APPLICATION_JSON_VALUE} to its @RequestMapping.

看起来Spring正在尝试生成XML,如果它在类路径上找到JAXB实现,我认为它会默认。如果您不需要从这个方法生成XML,您可以添加生成= {MediaType。APPLICATION_JSON_VALUE @RequestMapping }。

#1


10  

You've probably solved this by now, but since I have this working I thought I'd add the solution to at least one of your problems for anyone else in a similar boat.

你现在可能已经解决了这个问题,但是既然我有了这个工作,我想我应该把这个解决方案至少添加到你的一个问题中。

Type mismatch: cannot convert from ResponseEntity<PagedResources> to HttpEntity<PagedResources<PersonDTO>>:

类型不匹配:不能将ResponseEntity 转换为HttpEntity >:

To solve this, add an additional type parameter to your return type:

要解决这个问题,请在返回类型中添加一个额外的类型参数:

@RequestMapping(value = REQUEST_MAPPING_LIST, method = RequestMethod.GET)
public HttpEntity<PagedResources<PersonDTO>> persons(final Model model, @ModelAttribute final PersonCriteria searchCriteria,
        final Pageable pageable, final PagedResourcesAssembler assembler) {
    ...
}

com.sun.istack.SAXException2: unable to marshal type "org.springframework.hateoas.Resource" as an element because it is not known to this context.]

com.sun.istack。SAXException2:无法编组类型“org.springframework.hateoas”。资源“作为一个元素,因为它不知道这个上下文。”

It looks like Spring is trying to produce XML, which I think it does by default if it finds a JAXB implementation on the classpath. If you don't need to produce XML from this method, you could add produces = {MediaType.APPLICATION_JSON_VALUE} to its @RequestMapping.

看起来Spring正在尝试生成XML,如果它在类路径上找到JAXB实现,我认为它会默认。如果您不需要从这个方法生成XML,您可以添加生成= {MediaType。APPLICATION_JSON_VALUE @RequestMapping }。