Assuming I have a combo box in Vaadin CrudEditor
so this will be the code of the combo box part:
ComboBox<Driver> driversComboBox = new ComboBox<>("Drivers");
ComboBox.ItemFilter<Driver> filter = (driver, filterString) ->
driver.getFullName().toLowerCase().contains(filterString.toLowerCase());
driversComboBox.setItems(filter, driverService.findAll());
driversComboBox.setItemLabelGenerator(Driver::getFullName);
and this is the binder:
binder.forField(driversComboBox).asRequired().bind(Transporter::getDrivers, Transporter::setDrivers);
but this binder is wrong, I get this error:
Bad return type in method reference: cannot convert java.util.Set<org.vaadin.webinar.security.sampleapp.Entity.Driver> to org.vaadin.webinar.security.sampleapp.Entity.Driver
So, Transporter model:
public class Transporter extends AbstractEntity{
...
#OneToMany(mappedBy = "transporter", fetch = FetchType.EAGER, cascade = {CascadeType.ALL}, targetEntity = Driver.class)
private Set<Driver> drivers = new HashSet<>();
}
So, in short, How to bind the combobox with a List?
Thanks
There is no Vaadin component but there is a 3rd party component https://vaadin.com/directory/component/multiselect-combo-box
But be aware that this component currently doesn't work with Vaadin 22. It works fine until Vaadin 21.
Related
Use Case: A user can CRUD multiple choice questions using a single page web application written in JavaScript.
Creating a new question and adding some options all happens within the browser / Frontend (FE).
The FE creates and uses temporary ids ("_1", "_2", ...) for both the question and all the options until the user clicks a save button.
When saving the newly created question the FE sends a JSON containing the temporary ids to the backend
As result the FE expects a 201 CREATED containing a map temporary id -> backend id to update its ids.
The user decides to add another Option (which on the FE side uses a temporary id again)
The user clicks save and the FE sends the updated question with a mixture of backend ids (for the question and the existing options) and a temporary id (for the newly created option)
To update the id of the the newly created option, the FE expects the reponse to contain the mapping for this id.
How should we implement the counterpart for the last part (5-7 adding an option) on the backend side?
I try this, but I cannot get the child ids after persistence.
Entities
#Entity
public class Question {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy = "config", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Option> options = new ArrayList<>();
// ...
}
#Entity
public class Option {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "question_id", nullable = false)
private Question question;
public Option(Long id, Config config) {
this.id = id;
this.question = question;
}
// ...
}
Controller
#RestController
#RequestMapping("/questions")
public class AdminQuestionsController {
#Autowired
private QuestionRepository questionRepo;
#Autowired
private OptionRepository optionRepo;
#PutMapping("/{id}")
#ResponseStatus(HttpStatus.OK)
public QuestionDTO updateQuestion(#PathVariable("id") String id, #RequestBody QuestionDTO requestDTO) {
Question question = questionRepo.findOneById(Long.parseLong(id));
// will hold a mapping of the temporary id to the newly created Options.
Map<String, Option> newOptions = new HashMap<>();
// update the options
question.getOptions().clear();
requestDTO.getOptions().stream()
.map(o -> {
try { // to find the existing option
Option theOption = question.getOptions().stream()
// try to find in given config
.filter(existing -> o.getId().equals(existing.getId()))
.findAny()
// fallback to db
.orElse(optionRepo.findOne(Long.parseLong(o.getId())));
if (null != theOption) {
return theOption;
}
} catch (Exception e) {
}
// handle as new one by creating a new one with id=null
Option newOption = new Option(null, config);
newOptions.put(o.getId(), newOption);
return newOption;
})
.forEach(o -> question.getOptions().add(o));
question = questionRepo.save(question);
// create the id mapping
Map<String, String> idMap = new HashMap<>();
for (Entry<String, Option> e : newOptions.entrySet()) {
idMap.put(e.getKey(), e.getValue().getId());
// PROBLEM: e.getValue().getId() is null
}
return QuestionDTO result = QuestionDTO.from(question, idMap);
}
}
In the controller I marked the Problem: e.getValue().getId() is null
How should such a controller create the idMap?
It would be best if you save each option individually and then save the generated Id on the map.
I did the test below and it works perfectly.
#Autowired
void printServiceInstance(QuestionRepository questions, OptionRepository options) {
Question question = new Question();
questions.save(question);
question.add(new Option(-1L, question));
question.add(new Option(-2L, question));
question.add(new Option(-3L, question));
question.add(new Option(-4L, question));
Map<Long, Long> idMap = new HashMap<>();
question.getOptions().stream()
.filter(option -> option.getId() < 0)
.forEach(option -> idMap.put(option.getId(), options.save(option).getId()));
System.out.println(idMap);
}
Console out:
{-1=2, -2=3, -3=4, -4=5}
UPDATED:
Or will be a better code style if the front end just control de order of the options, and get the new ids based on the order of the unsaved options.
Option:
#Column(name = "order_num")
private Integer order;
public Option(Long id, Integer order, Question question) {
this.id = id;
this.question = question;
this.order = order;
}
Update example:
#Autowired
void printServiceInstance(QuestionRepository questions, OptionRepository options) {
Question question = new Question();
Question merged = questions.save(question);
merged.add(new Option(-1L, 1, merged));
merged.add(new Option(-2L, 2, merged));
merged.add(new Option(-3L, 3, merged));
merged.add(new Option(-4L, 4, merged));
questions.save(merged);
System.out.println(questions.findById(merged.getId()).get().getOptions());//
}
Console out: [Option [id=2, order=1], Option [id=3, order=2], Option [id=4, order=3], Option [id=5, order=4]]
Note there is no need of a map to control the new ids, the front-end should know by get it by the order of the options.
you can create additional field in both Question and Option class and marked as #Transient to make sure it's not persisted.
class Question {
....
private String id; // actual data field
#Transient
private String tempId;
// getter & setter
}
Initially when UI sends data, set tmpId and persist your Object. On successful operation, id will have actual id value. Now, let's create mapping (tmpId -> actualId).
Map<String, String> mapping = question.getOptions().stream()
.collect(Collectors.toMap(Option::getTmpId, Option::getId, (first, second) -> second));
mapping.put(question.getTmpId(), question.getId());
As you want only newly created object, we can do that two ways. Either add filter while creating mapping or remove later.
As mentioned, after first save, UI will update tmpId with actual Id and on next update, you will get a mix (actual for already saved, and tempId for newly created). In case of already saved, tmpId and actualId will be same.
mapping.entrySet().removeIf(entry -> entry.getKey().equals(entry.getValue()));
Regarding your controller code, you are clearing all existing option before adding a new option. If you are getting Question Object which has id (actual) field already populated, you can persist it directly. it will not affect anything. Additional if it has some change in that, that will be persisted.
Regarding your controller code, As you are clearing
question.getOptions().clear();
After this, you can simply add new options.
question.setOptions(requestDTO.getOptions());
question = questionRepo.save(question);
I hope it helps now.
So, you need to distinct FE-generated ID from BE-generated ?
You can
use negative ID on FE-generated, positive on BE
choose special prefix/suffix for FE-generated("fe_1", "fe_2", ...)
keep in Session list of already mapped IDs (server-side)
keep list of FE-generated ID and send it with data on POST (client-side)
In any case, beware of collisions when mixing two generators of ID.
I use hibernate 5.0.8 and spring data jpa 1.10.1
Given these entities
class Model {
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
#JoinColumn(nullable = false)
private Configuration configuration;
//more fields and methods
}
class Configuration {
#OneToMany(mappedBy = "configuration", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Setting> settings = new ArrayList<>();
//more fields and methods
//settings is never assigned again - I use settings.add(...) and settings.clear()
}
class Setting {
#ManyToOne
#JoinColumn(nullable = false)
private Configuration configuration;
//more fields and methods
}
Model is the master, but multiple models can use the same configuration. The cascading on configuration in Model is required, because if I change anything in the Configuration, I want it to be applied in all Models using this Configuration
Now when I retrieve an existing Model with a Configuration that has settings, and save this Model, without applying any changes to the settings I get following exception
#Transactional
public void doSomething() {
Model model = modelRepository.findOne(0);
//change something in the model, but no changes are made in its configuration
//or do nothing
modelRepository.save(model);
}
I get the following exception
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: Configuration.settings
I suspect this has something to do with the settings being lazy loaded and hibernate trying to merge an empty list into the configuration.
What am I doing wrong?
Check the getters and setters of the objects you are trying to clean up as orphans while dereferencing:
Try and use :
public void setChildren(Set<Child> aSet) {
//this.child= aSet; //Results in this issue
//change to
this.child.clear();
if (aSet != null) {
this.child.addAll(aSet);
} }
The problem was being caused by using enableLazyInitialization from the hibernate-enhance-maven-plugin. I still have no idea why it was causing this error, but removing this plugin resolved the issue.
I used this plugin, because I wanted to lazy load a large String field in Model that I would cache in the application. I will now change it to a OneToOne relation that is fetched lazily.
Hey everyone I am fairly new to Neo4j and am having an issue querying my repositories.
Repository is the follow:
public interface NodeOneRepository extends GraphRepository<NodeOne> {
List<NodeOne> findByNodeTwoNodeThreeAndActiveTrue(NodeThree nodeThree);
}
My entities are the following:
#NodeEntity(label = "NodeOne")
public class NodeOne {
#GraphId
private Long id;
private Boolean active = TRUE;
#Relationship(type = "IS_ON")
private NodeTwo nodeTwo;
}
#NodeEntity(label = "NodeTwo")
public class NodeTwo {
#GraphId
private Long id;
#Relationship(type = "CONTAINS", direction = "INCOMING")
private NodeThree nodeThree;
#Relationship(type = "IS_ON", direction = "INCOMING")
private List<NodeOne> nodeOnes = new ArrayList<>();
}
#NodeEntity(label = "NodeThree")
public class NodeThree {
#GraphId
private Long id;
#Relationship(type = "CONTAINS")
private List<NodeTwo> nodeTwos = new ArrayList<>();
}
Getters & Setters omitted. When I call the method I get an empty list. Is there something I am doing incorrectly?
You didn't describe exactly what you wanted to achieve, but I can see two problems:
Problem 1:
The current version of Spring Data Neo4j and OGM only allow nested finders, that is, finders that specify a relationship property, to one depth.
Supported
findByNodeTwoSomePropertyAndActiveTrue(String relatedNodePropertyValue)
Not Supported
findByNodeTwoNodeThree //Nesting relationships in finders is not supported
Problem 2:
Derived Finders Allow Matching Properties and Nested Properties. Not a whole instance of that class.
You can probably achieve what you would like using a custom query.
#Query("custom query here")
List<NodeOne> findByNodeTwoNodeThreeAndActiveTrue(NodeThree nodeThree);
If you need help to write a custom query, you can post another question or join the neo4j-users public slack channel.
I have a database service using Spring Boot 1.5.1 and Spring Data Rest. I am storing my entities in a MySQL database, and accessing them over REST using Spring's PagingAndSortingRepository. I found this which states that sorting by nested parameters is supported, but I cannot find a way to sort by nested fields.
I have these classes:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
#ManyToOne
protected Address address;
#ManyToOne(targetEntity = Name.class, cascade = {
CascadeType.ALL
})
#JoinColumn(name = "NAME_PERSON_ID")
protected Name name;
#Id
protected Long id;
// Setter, getters, etc.
}
#Entity(name = "Name")
#Table(name = "NAME")
public class Name{
protected String firstName;
protected String lastName;
#Id
protected Long id;
// Setter, getters, etc.
}
For example, when using the method:
Page<Person> findByAddress_Id(#Param("id") String id, Pageable pageable);
And calling the URI http://localhost:8080/people/search/findByAddress_Id?id=1&sort=name_lastName,desc, the sort parameter is completely ignored by Spring.
The parameters sort=name.lastName and sort=nameLastName did not work either.
Am I forming the Rest request wrong, or missing some configuration?
Thank you!
The workaround I found is to create an extra read-only property for sorting purposes only. Building on the example above:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
// read only, for sorting purposes only
// #JsonIgnore // we can hide it from the clients, if needed
#RestResource(exported=false) // read only so we can map 2 fields to the same database column
#ManyToOne
#JoinColumn(name = "address_id", insertable = false, updatable = false)
private Address address;
// We still want the linkable association created to work as before so we manually override the relation and path
#RestResource(exported=true, rel="address", path="address")
#ManyToOne
private Address addressLink;
...
}
The drawback for the proposed workaround is that we now have to explicitly duplicate all the properties for which we want to support nested sorting.
LATER EDIT: another drawback is that we cannot hide the embedded property from the clients. In my original answer, I was suggesting we can add #JsonIgnore, but apparently that breaks the sort.
I debugged through that and it looks like the issue that Alan mentioned.
I found workaround that could help:
Create own controller, inject your repo and optionally projection factory (if you need projections). Implement get method to delegate call to your repository
#RestController
#RequestMapping("/people")
public class PeopleController {
#Autowired
PersonRepository repository;
//#Autowired
//PagedResourcesAssembler<MyDTO> resourceAssembler;
#GetMapping("/by-address/{addressId}")
public Page<Person> getByAddress(#PathVariable("addressId") Long addressId, Pageable page) {
// spring doesn't spoil your sort here ...
Page<Person> page = repository.findByAddress_Id(addressId, page)
// optionally, apply projection
// to return DTO/specifically loaded Entity objects ...
// return type would be then PagedResources<Resource<MyDTO>>
// return resourceAssembler.toResource(page.map(...))
return page;
}
}
This works for me with 2.6.8.RELEASE; the issue seems to be in all versions.
From Spring Data REST documentation:
Sorting by linkable associations (that is, links to top-level resources) is not supported.
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#paging-and-sorting.sorting
An alternative that I found was use #ResResource(exported=false).
This is not valid (expecially for legacy Spring Data REST projects) because avoid that the resource/entity will be loaded HTTP links:
JacksonBinder
BeanDeserializerBuilder updateBuilder throws
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of ' com...' no String-argument constructor/factory method to deserialize from String value
I tried activate sort by linkable associations with help of annotations but without success because we need always need override the mappPropertyPath method of JacksonMappingAwareSortTranslator.SortTranslator detect the annotation:
if (associations.isLinkableAssociation(persistentProperty)) {
if(!persistentProperty.isAnnotationPresent(SortByLinkableAssociation.class)) {
return Collections.emptyList();
}
}
Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface SortByLinkableAssociation {
}
At project mark association as #SortByLinkableAssociation:
#ManyToOne
#SortByLinkableAssociation
private Name name;
Really I didn't find a clear and success solution to this issue but decide to expose it to let think about it or even Spring team take in consideration to include at nexts releases.
Please see https://stackoverflow.com/a/66135148/6673169 for possible workaround/hack, when we wanted sorting by linked entity.
I have a one to many relationship: Order and Items. An Order may have lots of items
The code below (all code) is auto generated by Spring Template project:
On the Order Side:
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="ORDER_ID")
private Collection<Item> items = new LinkedHashSet<Item>();
On the Item Side:
#ManyToOne
private Order order;
Test code that works:
order.getItems().add(new Item());
session.save(order)
Order other = (Order) session.get(Order.class, order.getId());
assertEquals(1, other.getItems().size());
While searching through the internet and various sites with tutorials on 1 <---> Many relationship I usually find code such as example: hibernate one to many
The One Side (Stock)
#OneToMany(fetch = FetchType.LAZY, mappedBy = "stock")
public Set<StockDailyRecord> getStockDailyRecords() {
return this.stockDailyRecords;}
The many Side (Stock Record)
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "STOCK_ID", nullable = false)
public Stock getStock() {
return this.stock;}
Why is the JoinColumn Annotation placed differently?The owner of the relationship according to spring code is on the side of the One and not the many. why is that?
Althouth the relationship does not have the mappedBy atttibute at all so one could say that is one direction from order to items it seems bidirectional. i.e order.getItems().add(new Item) and order.getItems().iterator().next().getOrder() works as well!! On top of that the tables created in the actual database seem ok.
If we refactor the first code (see below the refactored) and place the annotations as the tutorials suggest for bidirectional mapping then 2/3 of the tests fail because no items are saved in the database (Assertion error expected 1 but was 0 when using order.getItems().add(new Item())) .Note that the spring tempate test code is left unchanged.Why?
Does it have to do with the annotations being placed on get methods vs annotations placed on private fields?
What is the proper way of saving Items and Order in database (java code)?
Thanks
The code refactored that fails the tests:
The One Side:
#OneToMany(cascade=CascadeType.ALL,mappedBy="order")
private Collection<Item> items = new LinkedHashSet<Item>();
The many side:
#ManyToOne
#JoinColumn(name="ORDER_ID")
private Order order;