I got an exception while trying to insert new object.
Logcat:
java.lang.IllegalStateException: Source entity has no ID (should have been put before)
at io.objectbox.relation.ToMany.internalCheckApplyToDbRequired(ToMany.java:599)
Insert function:
public static void setSyncData(long contactId, SyncerData syncData) {
final Box<SyncerData> box = getObjectBoxStore().boxFor(SyncerData.class);
SyncerData syncerData = box.query().equal(SyncerData_.id, contactId).build().findFirst();
if (syncerData == null) {
syncerData = new SyncerData();
syncerData.setPhoneOrIdKey(ContactData.generateId(Phone.EMPTY, contactId));
}
syncerData.setSyncerDetailsToMany(syncData.getSyncerDetailsToMany());
box.put(syncerData);
}
What that's mean, that I can't put new object ToMany list before I added to object?
I think it's related to https://github.com/objectbox/objectbox-java/issues/104.
In essence setting a relation to a new plain List is somewhat problematic because ObjectBox lacks the change tracking, which is available in the ToManyclass. We'll look how to sync that in a future release.
Please try something like the following:
toMany.clear();
toMany.addAll(newList);
Related
I have the following code that first check record and if found delete that record and flush changes to the database. However, when I debug, I see that it does not reflect changes to the database when debugger hit the next code block (final Stock stock = new Stock();).
#Transactional
public CommandDTO createOrUpdate(StockRequest request) {
stockRepository.findByBrandUuidAndProductUuid(
request.getBrandUuid(),
request.getProductUuid())
.ifPresent(stock -> {
stockRepository.delete(stock);
stockRepository.flush();
});
final Stock stock = new Stock();
if (request.isOutOfStock()) {
stock.setBrandUuid(request.getBrandUuid());
stock.setProductUuid(request.getProductUuid());
stock.save(stock);
}
return CommandDTO.builder().uuid(stock.getUuid()).build();
}
So, what is the mistake in this approach?
JPA doesn't supports final field.
You can use two alternative solution for immutable class.
use #Immutable at entity class.
change entity class fields having only a getter.
I have written a custom https://mindbug.in/vaadin/vaadin-dataprovider-example/ CallBackDataProvider that I based on this link here, which is used for a multi-select combo box (an addon https://github.com/bonprix/vaadin-combobox-multiselect from Vaadin's addon directory) for the purpose of providing a item lazy loading.
According to the addon's clear() and selectAll(), it expects a ListDataProvider. I've already set the component's data provider to used the custom data provider above. Whenever a clear or selectAll function is triggered, the Class Cast Exception is being thrown. It is expecting a ListDataProvider.
The very straightforward workaround for this case is to disable the clear and selectAll method by setting the boolean flag to false, but from the user's point of view, this will not be flexible.
Another step attempted is to to convert the stream into a Collection List, yet, it didn't work. It still throws an error.
This is the custom CallbackDataProvider, extended from the AbstractBackendDataProvider:
public ItemDataProvider(ReceiptService receiptService) {
if(receiptService != null){
this.receiptService = receiptService;
}else {
this.receiptService = new ReceiptService();
}
}
#Override
protected Stream<SkusSelectBox> fetchFromBackEnd(Query<SkusSelectBox, String> query) {
stream = receiptService.fetchSkus(query.getFilter().orElse(null), query.getLimit(), query.getOffset(), query.getSortOrders()).stream();
return stream;
}
#Override
protected int sizeInBackEnd(Query<SkusSelectBox, String> query) {
return receiptService.countSkus(query.getFilter().orElse(null));
}
#Override
public Object getId(SkusSelectBox item) {
return item.getItemId();
}
public Stream<SkusSelectBox> getStream(){
return stream;
}
The SkuSelectBox is a simple two string attribute object that retrieves the id and the name.
For this component, I have set the following at the view page:
ItemDataProvider itemDataProvider = new ItemDataProvider(receiptService);
ComboBoxMultiselect<SkusSelectBox> skuSelect = new ComboBoxMultiselect<>("Items");
skuSelect.setPlaceholder("Choose Items");
skuSBox.add(new SkusSelectBox("0", "No data found"));
skuSelect.setWidth(80, Unit.PERCENTAGE);
skuSelect.setRequiredIndicatorVisible(true);
skuSelect.setItemCaptionGenerator(SkusSelectBox::getItemName);
skuSelect.setSelectAllButtonCaption("Select All");
skuSelect.setClearButtonCaption("Clear");
skuSelect.showSelectAllButton(true);
skuSelect.showClearButton(true);
skuSelect.setDataProvider(itemDataProvider);
skuSelect.getDataProvider().refreshAll();
skuSelect.isReadOnly();
skuSelect.setPageLength(20);
if(skuSBox.size() <=1 ){
skuSelect.showSelectAllButton(false);
//skuSelect.showClearButton(false);
}
skuSelect.setResponsive(true);
The selectAll and clear methods are very similar except for the very end of the method:
#Override
public void selectAll(final String filter) {
final ListDataProvider<T> listDataProvider = ((ListDataProvider) getDataProvider());
final Set<String> addedItems = listDataProvider.getItems()
.stream()
.filter(t -> {
final String caption = getItemCaptionGenerator().apply(t);
if (t == null) {
return false;
}
return caption.toLowerCase()
.contains(filter.toLowerCase());
})
.map(t -> itemToKey(t))
.collect(Collectors.toSet());
updateSelection(addedItems, new HashSet<>(), true);
updateSelection(new HashSet<>(), removedItems, true); (this is for clear method)
}
Basically the class cast exception is shown in this error message, referring to either the clear or selectAll, whichever method I was invoking:
java.lang.ClassCastException: com.igi.sycarda.dashboard.hib.utils.ItemDataProvider cannot be cast to com.vaadin.data.provider.ListDataProvider
at org.vaadin.addons.ComboBoxMultiselect$1.clear(ComboBoxMultiselect.java:224)
I'm looking at the selectAll or clear method, when invoked to work as usual as if not using a CallbackDataProvider.
Until the next patch release for the addon is released, I need to put in a workaround for this problem, how can I convert a custom provider to a ListDataProvider either in a quick dirty way or a cleaner way if required?
UPDATE: Normally, I would do a direct fetch from the service class, but when tested with a tenant that has about 20K of item records, the loading of the page and the specific component box is quite slow to load. That CallbackDataProvider is to test this will work for those big amount of records.
The idea with a list data provider is that all items are loaded into memory. It is possible to load all items from a database into memory and then use that to create a list data provider. This does on the other hand defeat the purpose of having a callback data provider.
It's probably more straightforward for you to fetch the items into a list directly from your receiptService rather than going through the existing data provider.
Since there are restrictions or blocks that cause error to approach I was doing, someone just suggested to me to create a view derived from the tables / columns required and used them instead of the normal tables.
After creating a view, I just reverted and removed these lines below to the usual implementation:
skuSelect.setDataProvider(itemDataProvider);
skuSelect.getDataProvider().refreshAll();
skuSelect.isReadOnly();
skuSelect.setPageLength(20);
if(skuSBox.size() <=1 ){
skuSelect.showSelectAllButton(false);
//skuSelect.showClearButton(false);
}
At the time of writing this, we've tested it an hour ago and it solves the problem without sacrificing the performance time taken and creating an additional component. In terms of time measurement, a 20K result set in a view loads in less than 10 seconds vs 7-9 minutes previously.
I have implemented by project using Spring-Data-Rest. I am trying to do an update on an existing record in a table. But when I try to send only a few fields instead of all the fields(present in Entity class) through my request, Spring-Data-Rest thinking I am sending null/empty values. Finally when I go and see the database the fields which I am not sending through my request are overridden with null/empty values. So my understanding is that even though I am not sending these values, spring data rest sees them in the Entity class and sending these values as null/empty. My question here is, is there a way to disable the fields when doing UPDATE that I am not sending through the request. Appreciate you are any help.
Update: I was using PUT method. After reading the comments, I changed it to PATCH and its working perfectly now. Appreciate all the help
Before update, load object from database, using jpa method findById return object call target.
Then copy all fields that not null/empty from object-want-to-update to target, finally save the target object.
This is code example:
public void update(Object objectWantToUpdate) {
Object target = repository.findById(objectWantToUpdate.getId());
copyNonNullProperties(objectWantToUpdate, target);
repository.save(target);
}
public void copyNonNullProperties(Object source, Object target) {
BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
}
public String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] propDesList = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<String>();
for(PropertyDescriptor propDesc : propDesList) {
Object srcValue = src.getPropertyValue(propDesc.getName());
if (srcValue == null) {
emptyNames.add(propDesc.getName());
}
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
You can write custom update query which updates only particular fields:
#Override
public void saveManager(Manager manager) {
Query query = sessionFactory.getCurrentSession().createQuery("update Manager set username = :username, password = :password where id = :id");
query.setParameter("username", manager.getUsername());
query.setParameter("password", manager.getPassword());
query.setParameter("id", manager.getId());
query.executeUpdate();
}
As some of the comments pointed out using PATCH instead of PUT resolved the issue. Appreciate all the inputs. The following is from Spring Data Rest Documentation:
"The PUT method replaces the state of the target resource with the supplied request body.
The PATCH method is similar to the PUT method but partially updates the resources state."
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.hiding-repository-crud-methods
Also, I like #Tran Quoc Vu answer but not implementing it for now since I dont have to use custom controller. If there is some logic(ex: validation) involved when updating the entity, I am in favor of using the custom controller.
We persist a graph in a piece of code and then have another code, that tries to retrieve it. We open our transacitons with this Spring bean. Anyone who wants to access the database always calls the getGraph() method of this bean.
public class OrientDatabaseConnectionManager {
private OrientGraphFactory factory;
public OrientDatabaseConnectionManager(String path, String name, String pass) {
factory = new OrientGraphFactory(path, name, pass).setupPool(1,10);
}
public OrientGraphFactory getFactory() {
return factory;
}
public void setFactory(OrientGraphFactory factory) {
this.factory = factory;
}
/**
* Method returns graph instance from the factory's pool.
* #return
*/
public OrientGraph getGraph(){
OrientGraph resultGraph = factory.getTx();
resultGraph.setThreadMode(OrientBaseGraph.THREAD_MODE.ALWAYS_AUTOSET);
return resultGraph;
}
}
(I was unable to quite understand the thread_mode fully, but I think it should not be related to the problem.)
The code, that persists the graph commits and shuts down, as you can see here:
OrientDatabaseConnectionManager connMan; //this is an injected bean from above.
public boolean saveGraphToOrientDB(
SparseMultigraph<SocialVertex, SocialEdge> graph, String label) {
boolean isSavedCorrectly = false;
OrientGraph graphO = connMan.getGraph();
try {
graphDBinput.saveGraph(graph, label, graphO);
// LOG System.out.println("Graph was saved with label "+label);
isSavedCorrectly = true;
} catch (AlreadyUsedGraphLabelException ex) {
Logger.getLogger(GraphDBFacade.class.getName()).log(Level.SEVERE, null, ex);
} finally {
graphO.shutdown(); //calls .commit() automatically normally, but commit already happens inside.
}
return isSavedCorrectly;
}
This commit works well - the data are always persisted, I checked everytime in the orientdb admin interface, and the first persisted graph is always viewable okay. It might be important to note, that during the saving the label used defines new class (thus modifying schema, as I understand it) and uses it for the persisted graph.
The retrieval of the graph looks something like this:
#Override
public SocialGraph getSocialGraph(String label) {
OrientGraph graph = connMan.getGraph();
SocialGraph socialGraph = null;
try {
socialGraph = new SocialGraph(getAllSocialNodes(label, graph), getAllSocialEdges(label, graph));
} catch (Exception e) {
logger.error(e);
} finally {
graph.shutdown();
}
return socialGraph;
}
public List<Node> getAllSocialNodes(String label, OrientGraph graph) {
return constructNodes(graphFilterMan.getAllNodesFromGraph(label, graph));
}
public Set<Vertex> getAllNodesFromGraph(String graphLabel, OrientGraph graph) {
Set<Vertex> labelledGraph = new HashSet<>();
try{
Iterable<Vertex> configGraph = graph.getVerticesOfClass(graphLabel);
for(Vertex v : configGraph){ //THE CODE CRASHES HERE, WITH "CLASS WITH NAME graphLabel DOES NOT EXIST
labelledGraph.add(v);
}
} catch(Exception ex){
logger.error(ex);
graph.rollback();
}
return labelledGraph;
}
So the problem is, that when we persist a new graph with a new class, say "graph01" and then we want to retrieve it, it goes okay. Later, we create a "graph02" and we want to retrieve it, but it crashes, as commented above - OrientDb tells you, that the class with "graph02" name does not exist.
It does exist in the admin interface at the time, however, when I debug, the class actually is not in the schema right after call of factory.getTx()
Right at the beginning, when we get a transaction graph instance from the factory, we get a graph with a context in which the rawGraph's underlying database's metadata have the schema proxy delegate schema shared classes WITHOUT the new class, which I can apparently see commited in the database.
Or here on picture:
There should be one more class in the schema. The one that was persisted (and commited ) a while ago - which can also be seen in the orientDb admin interface (not present in the variable)
What I presume is happening is that the pool, from which the factory gets the transaction has somewhat cached schema or something. It does not refresh the schema, when we add a new class.
Why does the schema not show the new class, when we are trying to get the new graph out? Does schema not get refreshed?
I found here in schema documentation that
NOTE: Changes to the schema are not transactional, so execute them outside a transaction.
So should we create the new class outside a transaction and then we would get an update in the schema in the context?
//Maybe I am understanding the concepts wrong - I got in contact with OrientDb just yesterday and I am to find out the problem in an already written code.
Db we use is a remote:localhost/socialGraph
OrientDB of version 1.7.4
We noticed in our code about the same issue, schema changes aren't visible in pooled connections.
We also have a sort of factory that gets a connection. What we do is keep a schema version number, and each time we have some operation that changes the schema, we bump the number and when a new connection is opened, we check the schema version, if it is changed.
When the schema is changed, we reload the schema, close the pool and recreate it. The method is proven for us to work (we are currently on version 2.0.15).
Here's the relevant code:
private static volatile int schemaVersion = -1;
private OPartitionedDatabasePool pool;
protected void createPool() {
pool = new OPartitionedDatabasePool(getUrl(), getUsername(), getPassword());
}
#Override
public synchronized ODatabaseDocumentTx openDatabase() {
ODatabaseDocumentTx db = pool.acquire();
//DatabaseInfo is a simple class put in a static contect that holds the schema version.
DatabaseInfo databaseInfo = CurrentDatabaseInfo.getDatabaseInfo();
ODocument document = db.load((ORID) databaseInfo.getId(), "schemaVersion:0", true);
Integer version = document.field("schemaVersion");
if (schemaVersion == -1) {
schemaVersion = version;
} else if (schemaVersion < version) {
db.getMetadata().getSchema().reload();
schemaVersion = version;
pool.close();
createPool();
db = pool.acquire();
}
return db;
}
In the end the problem was, that we had two liferay projects, each had its own spring application context in its WAR file and when we deployed these projects as portlets within Liferay, the two projects created two contexts, in each having one OrientDatabaseConnectionManager.
In one context the schema was being changed. And even though I reset the connection and reloaded the schema, it only happened with the connection manager / factory in one context. The retrieving of the graph was happening in the portlet of the other project though, resulting in an outdate schema (which was not reloaded, because the reloading happened in the other spring context) - thus the error.
So you have to be careful - either share one spring application context with beans for all your portlets (which is possible by having a parent application context, you can read more about it here)
OR
check for changes in the schema from within the same project which you will also use to retrieve the data later.
I have a database model class Project, and it has two embedded object fields:
#Embedded
public ContacterInfo contacter = new ContacterInfo();
#Embedded
public CompanyInfo company = new CompanyInfo();
'cause I don't want to check is null every time I use company and contacter, so I decided to create them anyway.
What I expected is, when there's nothing for contacter in database, Java would create a new ContacterInfo for me, and then I can just use it for new data. But in fact, I found contacter still could be set to null. I suspect that JPA load null from database and override my new create object with it.
How can I fix this ?
You can use the JPA Entity listeners (ObjectDB has a good explication). By example:
#PostLoad
void onPostLoad() {
if (contacter == null) {
contacter = new ContacterInfo();
}
if (company == null) {
company = new CompanyInfo();
}
}
Every time JPA load an instance of current entity, onPostLoad will be called.
Good luck!