Spring Data R2DBC does not support nested objects, so how correctly retrieve and join nested object to parent object? I have class Foo (child), Bar (Parent), BarDTO:
#Data
public class Foo {
#Id
private String id;
private String value;
}
#Data
public class Bar {
#Id
private String id;
private String fooId;
private String value;
}
#Data
public class BarDTO {
private String id;
private Foo foo;
private String value;
public BarDTO(Bar t2, Foo t1) {
this.id = t2.getId();
this.foo = t1;
this.value = t2.getValue();
}
}
Right now i have this solution, but i am not sure that this is the simplest solution and that i am working correctly with reactive streams:
#AllArgsConstructor
public class BarService {
private final FooRepository fooRepository;
private final BarRepository barRepository;
public Flux<BarDTO> getAllBar() {
return barRepository.findAll()
.collectList()
.flatMapMany(bars -> {
List<Mono<BarDTO>> monoBarList = new ArrayList<>();
for (Bar bar : bars) {
Mono<BarDTO> monoDTO = Mono.just(bar).zipWith(fooRepository.findById(bar.getFooId()))
.flatMap(result -> Mono.just(new BarDTO(result.getT1(), result.getT2())));
monoBarList.add(monoDTO);
}
return Flux.concat(monoBarList);
});
}
}
Related
I have a the below class:
#Data
public class PagedSchoolDto {
private final Integer count;
private final Map<String, List<School>> content;
private final String pagingState;
private final Boolean hasNext;
public PagedSchoolDto(final Slice<School> slice) {
this.content = slice.getContent().stream().collect(
Collectors.groupingBy(School::getId, () -> new TreeMap<>(new UUIDComparator()), Collectors.toList()));
this.count = slice.getContent().size();
this.hasNext = slice.hasNext();
this.pagingState = getPagingState(slice);
}
#Nullable
private static String getPagingState(final Slice<School> slice) {
if (slice.hasNext()) {
CassandraPageRequest pageRequest = (CassandraPageRequest) slice.nextPageable();
return pageRequest.getPagingState().toString();
} else {
return null;
}
}
}
Now, I want to make my code generic so that I can use this class for other object types as well like below:
#Data
public class PagedDto<T> {
private final Integer count;
private final Map<String, List<T>> content;
private final String pagingState;
private final Boolean hasNext;
public PagedDto(final Slice<T> slice) {
this.content = slice.getContent().stream().collect(
Collectors.groupingBy(<T>::getId, () -> new TreeMap<>(new UUIDComparator()), Collectors.toList()));
this.count = slice.getContent().size();
this.hasNext = slice.hasNext();
this.pagingState = getPagingState(slice);
}
#Nullable
private static <T> String getPagingState(final Slice<T> slice) {
if (slice.hasNext()) {
CassandraPageRequest pageRequest = (CassandraPageRequest) slice.nextPageable();
return pageRequest.getPagingState().toString();
} else {
return null;
}
}
}
All my classes have a property called ID which is type UUID so the comparator should work fine all generics types. The issue is I'm not sure how to write the Collectors.groupingBy's function and supplier code as the second snippet is giving compilation error.
Your generic type T is unspecified, e.g. 'Object' which does not provide the method getId. In order to access getId in the generic code, T must extend an interface with the getId method, which is implemented by any class that is used with PagedDto:
public interface WithId {
UUID getId();
}
public class School implements WithId {
private UUID id;
#Override
public UUID getId() {
return id;
}
public class PagedDto<T extends WithId> {
...
I want to know if it's possible to map a DTO to an entity class with a composite pk. I've been reading ModelMapper documentation about PropertyMap but I can't make it work.
Here is the code:
PlanDTO:
public class PlanDTO implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
private String formula;
private String frequency;
private String pricingtable;
// getters and setters omitted
PlanId:
#Embeddable
public class PlanId implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
public BillingPlanId() { }
public PlanId(Long id, String name) {
this.id = id;
this.name = name;
}
// getters and setters omitted
}
Plan:
#Entity
#Table(name = "plan")
public class Plan implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private PlanId id;
#Column(name = "formula")
private String formula;
#Column(name = "frequency")
private String frequency;
#Column(name = "pricingtable")
private String pricingTable;
public Plan() { }
//setters and getters omitted
}
Here is the ModelMapper configuration.
#Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setAmbiguityIgnored(true);
PropertyMap<PlanDTO, Plan> itemMap1 = new PropertyMap<PlanDTO, Plan>() {
protected void configure() {
map().setFormula(source.getFormula());
map().setFrequency(source.getFrequency());
map().setId(new Plan(source.getId(), source.getName()));
map().setPricingTable(source.getPricingtable());
}
};
modelMapper.addMappings(itemMap1);
}
But this happens at runtime debug image
Is there something wrong with the configuration? Do I miss something?
I am not quite sure what is your problem but mapping should be quite easy with a just one property mapping:
modelMapper.addMappings(new PropertyMap<PlanDTO, Plan>() {
#Override
protected void configure() {
map().getId().setName(source.getName());
}
});
All the other fields should be implicitly mapped by their name. Even the PlanId.id.
I have problems with persisting the following construct with JPA.
#MappedSuperclass
public abstract class SuperFoo {
#Embedded
private Bar bar;
public SuperFoo() {}
public SuperFoo(...) {
...
this.bar = new Bar("barKey");
}
...
public void setBar(Bar bar) {
this.bar = bar;
}
public Bar getBar() {
return bar;
}
}
#Embeddable
public class Bar {
#ElementCollection
Map<String, Long> durations;
#ElementCollection
Map<String, Integer> integers;
public Bar() {}
public Bar(String barKey) {
his.durations = new HashMap<String, Long>();
this.integers = new HashMap<String, Integer>();
durations.put(key, new Long(15));
integers.put(key, new Integer(10));
}
... setters and getters.....
}
#Entity
public class Foo extends SuperFoo {
#Id
private String id;
public Foo() {}
public Foo(String id, *superArguments*) {
super(*superArguments*);
this.id = id;
}
public String getId() { return id; }
public void setId(String id) {
this.id = id;
}
}
#Entity
public class FooContainer {
#Id
String id;
#OneToMany(cascade = CascadeType.ALL)
List<Foo> fooList;
public FooContainer() {}
public FooContainer(String id) {
this.id = id;
this.fooList = new ArrayList<>();
}
public void addFoo(Foo foo) {
this.fooList.add(foo);
}
public void setFooList(List<Foo> fooList) {this.fooList = fooList;}
public List<Foo> getFooList() { return fooList; }
}
Method where persisting takes place:
public void method() {
FooContainer fooContainer = fooContainerRepository.getOne(...);
fooContainer.addFoo(new Foo(...));
fooContainerRepository.save(fooContainer);
}
JPA creates the following tables (looks fine):
foo
foo_durations
foo_integers
Now, when I save an instance of Foo, everything gets persisted (fields in Foo). BUT I get no entries in foo_durations and foo_integers. Furthermore, I get no exceptions.
I did a little research myself and found the following:
"An embeddable class that is contained within an element collection must not contain an element collection."
I think, here, that is not the case because my embeddable class "Bar" is not contained within an element collection. So does anybody know, what I might have done wrong?
Thanks in advance!
Update:
I forgot to mention that the Bar Object inside SuperFoo is not set via constructor but calculated inside the constructor.
I use different NoSQL databases and depending on the database I need to name the "id" different. So for example in OrientDB the id is named "#rid"
#JsonProperty("#rid")
private String id;
And for MongoDB the id is named "_id"
#JsonProperty("#_id")
private String id;
I do not know what is wrong with the modern DB developers not just naming the id field "id" ^^. But now I have a problem. How can I dynamically serialize/deserialize the id field in some case as "#rid" and in another case as "_id"?
EDIT:
Based on rmullers suggestion I have tried to use mixins. So I have for example:
public interface IdMixins {
}
public interface MongoIdMixIn extends IdMixins {
#JsonProperty("_id") String getId();
#JsonProperty("_id") void setId(String id);
}
public interface OrientIdMixIn extends IdMixins{
#JsonProperty("#rid") String getId();
#JsonProperty("#rid") void setId(String id);
}
Where IdMixins is a completly empty interface just used to get more controll which interfaces can be passet to the mapper.
Then there is a class:
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#javaClass")
public abstract class AbstractBean implements Serializable {
private static final long serialVersionUID = -1286900676713424199L;
// #JsonProperty("#rid")
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
But when I run this simple test, the output is still "id":
public class MixinTest {
public static void main(String[] args) throws JsonProcessingException {
Foo f = new Foo();
f.setId("123");
f.setBar("lala");
ObjectMapper mapper = new ObjectMapper();
ObjectMapper m2 = mapper.copy();
m2.addMixInAnnotations(AbstractBean.class, MongoIdMixIn.class);
System.out.println(m2.writeValueAsString(f));
ObjectMapper m3 = mapper.copy();
m3.addMixInAnnotations(AbstractBean.class, OrientIdMixIn.class);
System.out.println(m3.writeValueAsString(f));
}
public static class Foo extends AbstractBean {
private String bar;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
}
Outputs:
{"#javaClass":"test.MixinTest$Foo","id":"123","bar":"lala","#class":"Foo"}
{"#javaClass":"test.MixinTest$Foo","id":"123","bar":"lala","#class":"Foo"}
Have you tried using http://wiki.fasterxml.com/JacksonMixInAnnotations? Then you can use an OrientDbMixin and a MongoDbMixin with different #JsonProperty configuration.
Update: Working example
public final class JacksonTest {
static final class ExampleBean {
private String id;
private String bar;
#JsonProperty("donotwanttoseethis")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
public interface MongoIdMixIn {
#JsonProperty("_id") String getId();
}
public interface OrientIdMixIn {
#JsonProperty("#rid") String getId();
}
private final static Logger LOG = LoggerFactory.getLogger();
public static void main(String[] args) throws JsonProcessingException {
ExampleBean bean = new ExampleBean();
bean.setId("1234");
bean.setBar("lala");
ObjectMapper m2 = new ObjectMapper();
m2.addMixInAnnotations(ExampleBean.class, MongoIdMixIn.class);
LOG.info(m2.writeValueAsString(bean));
ObjectMapper m3 = new ObjectMapper();
m3.addMixInAnnotations(ExampleBean.class, OrientIdMixIn.class);
LOG.info(m3.writeValueAsString(bean));
}
}
Suppose I have an entity User
public class User {
private String username;
private String password;
private Map<String, Object> settings = new HashMap<String, Object>();
//Getters & setters
}
Is there any way that I can use the key of the settings map in a query?
Eg:
Criterion crit = mySession.createCriterion(User.class);
crit.add(Restrictions.eq("settings.blah", 1234L));
List<User> results = crit.list();
Or access the map values via EL?
Eg:
<h:dataTable value="#{myBean.users}" var="user">
<h:column>
<h:outputText value="#{user.blah}"/>
</h:column>
</h:dataTable>
I can partially do this with a simple #OneToMany mapping, but that required my query to use both key and value, and the properties are not accessible through EL.
Is something like this possible? How would I map it with annotations?
Thanks
EDIT:
Essentially, what I am after is a 'vertical table' implementation so that I can have arbitrary fields, and still be able to use the Entity as a mapped Class. I do not know if this is even possible with hibernate.
I never user Object as a value of the map, you can use String with this mapping:
#Entity
public class User {
private int _id;
private Map<String, String> _settings;
public User() {
}
#Id
#GeneratedValue
public int getId() {
return _id;
}
public void setId(int id) {
_id = id;
}
#ElementCollection
#MapKeyColumn(name = "SETTINGS_KEY")
#CollectionTable(name = "USER_SETTINGS", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "SETTING")
public Map<String, String> getSettings() {
return _settings;
}
public void setSettings(Map<String, String> settings) {
_settings = settings;
}
}
If you need to use different type of columns then you must use a interface as value of the map like this:
#Entity
public class TestSettings {
private int _id;
private Map<String, SettingValue<?>> _settings;
public TestSettings() {
}
#Id
#GeneratedValue
public int getId() {
return _id;
}
public void setId(int id) {
_id = id;
}
#OneToMany
#MapKeyColumn(name = "SETTINGS_KEY")
#CollectionTable(name = "USER_SETTINGS", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "SETTING")
public Map<String, SettingValue<?>> getSettings() {
return _settings;
}
public void setSettings(Map<String, SettingValue<?>> settings) {
_settings = settings;
}
}
The SettingValue abstract class:
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
public abstract class SettingValue<V> implements Serializable {
private static final long serialVersionUID = 3355627640146408150L;
private Integer _id;
public SettingValue() {
super();
}
#Id #GeneratedValue
public Integer getId() {
return _id;
}
public void setId(Integer id) {
_id = id;
}
#Transient
public abstract V getValue();
public abstract void setValue(V value);
}
The settingValue String implementation:
#Entity
#PrimaryKeyJoinColumn(name="settingvalue_id", referencedColumnName="id")
public class TextSettingValue extends SettingValue<String> implements Serializable {
private String _value;
public TextSettingValue() {
super();
}
#Override
public void setValue(String value) {
_value = value;
}
#Override
public String getValue() {
return _value;
}
}
You have to create a SettingValue implementation for each type you want to support, in this way you get a table for each type with a column to hold your value.