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.