简体   繁体   中英

Fetching part of one to many property using Spring Data Projections

I want to return a tuple of Parent.id field and List<Child.id> .


Parent :

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Entity
public class Parent implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    @Column(name = "id")
    private Long parentId;

    //we actually use Set and override hashcode&equals
    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
    private List<Child> children = new ArrayList<>();

    public void addChild(Child child) {

        child.setParent(this);
        children.add(child);
    }

    public void removeChild(Child child) {

        child.setParent(null);
        children.remove(child);
    }

    public Long getParentId() {

        return id;
    }

    public List<Child> getReadOnlyChildren() {

        return Collections.unmodifiableList(children);
    }
}

Child :

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import java.io.Serializable;

@Entity
public class Child implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    @Column(name = "id")
    private Long childId;

    @ManyToOne
    @JoinColumn(name = "id")
    private Parent parent;

    public Long getChildId() {

        return id;
    }

    public Parent getParent() {

        return parent;
    }

    /**
     * Only for usage in {@link Parent}
     */
    void setParent(final Parent parent) {

        this.parent = parent;
    }
}

The Spring Data Projection:

import java.util.List;

interface IdAndChildrenIds {

    Long getParentId();

    List<ChildId> getChildren();
}

interface ChildId {

    Long getChildId();
}

The ParentRepository this is where problems begin:

import org.springframework.data.repository.CrudRepository;

public interface ParentRepository extends CrudRepository<Parent, Long> {

    IdAndChildrenIds findIdAndChildrenIdsById(Long id);
}

But that doesn't work because the property doesn't comply with JavaBean standard (getter getReadOnlyChildren instead of getChildren ), so I configured ObjectMapper to recognize private fields:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.List;

@Configuration
@EnableWebMvc
public class HibernateConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

        ObjectMapper mapper = new Jackson2ObjectMapperBuilder().build();
        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);

        converters.add(new MappingJackson2HttpMessageConverter(mapper));
    }
}

Then, it still doesn't work because the property is LAZY initialized and it cannot be fetched outside a transaction (and because I wrote spring.jpa.open-in-view=false in application.properties due to that being a better practice). So, I must specify explicit join using query and also must use aliases so Spring Data recognizes the properties:

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;

public interface ParentRepository extends CrudRepository<Parent, Long> {

    @Query("select " +
           "    c.parent.parentId   as parentId, " +
           "    c.childId as childId" +
           "from Child c inner join a.parent p " +
           "where p.parentId=:id")
    IdAndChildrenIds findIdAndChildrenIdsById(@Param("id") long id);
}

But this again doesn't work javax.persistence.NonUniqueResultException: result returns more than one elements because the specified select gives a list of tuples: List<{parentId, childId}> , while I want one tuple of {parentId, List<childId>} .

So, regarding this answer, I added @Value("#{target.parentId}") to Long getParentId(); . But that did not have any effect in my case. I still get NonUniqueResultException .

Then, I tried changing the return value of the method from IdAndChildrenIds to IdAndChildrenIds just to see if the error goes away, even though that solution would not help. But that didn't work either:

Could not write JSON: No serializer found for class org.springframework.aop.framework.DefaultAdvisorChainFactory and no properties discovered to create BeanSerializer

As I said, field visibility is already set to ANY .


Versions:

- Spring Boot 1.5.9.RELEASE
 - Spring Boot Starter Data JPA
 - Spring Boot Starter Web
 - Spring HATEOAS

Looking at this now, weird that I want parent id and ids of its children while knowing the parent id already.

interface ChildRepo{

  @org.spring...Query(value = "select id from children where parent_id = :parentId", nativeQuery = true)
  List<Long> findIdsByParentId(Long parentId);
}

@lombok.Value
class IdsDto{
  Long parentId;
  List<Long> childrenIds;

}

public IdsDto createTupleThing(Long parentId){
  return new IdsDto(parentId, childRepo.findIdsByParentId(parentId);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM