I use ActiveAndroid library in my project. In the model I have the following parameters:
onDelete = Column.ForeignKeyAction.CASCADE, onUpdate = Column.ForeignKeyAction.CASCADE
But it's impossible to find Insert cascade or something else.
My models has many relations and I need to save them all:
for (Transaction transaction : shoppingSession.getTransactionList()) {
transaction.setShoppingSession(shoppingSession);
TransactionDao.save(transaction);
}
public static Transaction save(Transaction transaction){
if (null != transaction.getTransactionItem().getItemInfo()){
transaction.getTransactionItem().getItemInfo().save();
}
if (null != transaction.getTransactionItem().save()){
transaction.getTransactionItem().save();
}
transaction.save();
return transaction;
}
It is inconvenient. I hope the library will be able to save all this in the relevant tables.
How can I implement it?
Thanks.
Related
Previously, when I was adding a entity to database with Hibernate I used to check that it hadn't already been added. But in an effort to improve performance I forgot this check and just tried to add without checking, as I was using saveOrUpdate() it was my understanding that if Hibernate found it was already added it would just update with and changes made by my save.
But instead it fails with
18/08/2018 21.58.34:BST:Errors:addError:SEVERE: Adding Error:Database Error:Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.jthink.songlayer.MusicBrainzReleaseWrapper#95f6f584-407f-4b26-9572-bb8c6e9c580a]
java.lang.Exception
at com.jthink.songkong.analyse.general.Errors.addError(Errors.java:28)
at com.jthink.songkong.exception.ExceptionHandling.handleHibernateException(ExceptionHandling.java:209)
at com.jthink.songkong.db.ReleaseCache.addToDatabase(ReleaseCache.java:394)
at com.jthink.songkong.db.ReleaseCache.add(ReleaseCache.java:65)
#Entity
public class MusicBrainzReleaseWrapper
{
#Id
private String guid;
#Version
private int version;
#org.hibernate.annotations.Index(name = "IDX__MUSICBRAINZ_RELEASE_WRAPPER_NAME")
#Column(length = 1000)
private String name;
#Lob
#Column(length = 512000)
private String xmldata;
public String getGuid()
{
return guid;
}
public void setGuid(String guid)
{
this.guid = guid;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getXmldata()
{
return xmldata;
}
public void setXmldata(String xmldata)
{
this.xmldata = xmldata;
}
}
private static boolean addToDatabase(Release release)
{
Session session = null;
try
{
session = HibernateUtil.beginTransaction();
//Marshall to String
StringWriter sw = new StringWriter();
Marshaller m = jc.createMarshaller();
m.marshal(release, sw);
sw.flush();
MusicBrainzReleaseWrapper wrapper = new MusicBrainzReleaseWrapper();
wrapper.setGuid(release.getId());
wrapper.setName(release.getTitle().toLowerCase(Locale.UK));
wrapper.setXmldata(sw.toString());
session.saveOrUpdate(wrapper);
session.getTransaction().commit();
MainWindow.logger.info("Added to db release:" + release.getId() + ":" + release.getTitle());
return true;
}
catch (ConstraintViolationException ce)
{
MainWindow.logger.warning("Release already exists in db:"+release.getId()+":"+release.getTitle());
return true;
}
catch(GenericJDBCException jde)
{
MainWindow.logger.log(Level.SEVERE, "Failed:" +jde.getMessage());
ExceptionHandling.handleDatabaseException(jde);
}
catch(HibernateException he)
{
MainWindow.logger.log(Level.SEVERE, "Failed:" +he.getMessage());
ExceptionHandling.handleHibernateException(he);
}
catch(Exception e)
{
MainWindow.logger.log(Level.WARNING,"Failed AddReleaseToDatabase:"+release.getId()+ ':' +e.getMessage(),e);
throw new RuntimeException(e);
}
finally
{
HibernateUtil.closeSession(session);
}
return false;
}
Used to check first before call to addToDatabase
if(ReleaseCache.get(release.getId())==null)
{
addToDatabase(release)
}
Hiberante object has 3 states for an Entity. They are:
- Transient Or New
- Detached (Objects are fetched from DB and hibernate session is closed)
- Persistent (Object are fetched from DB and hibernate session is open)
In saveOrUpdate method, it either save the transient object or update the detached/ persistent object.
In your code, you are trying to create Transient/New object and setting the old id in it. That's the reason you are getting above error. The correct way to fetch the object first using id and then update it.
The problem you are hitting is directly related to the Optimistic locking you have enabled through the #Version annotation on the MusicBrainzReleaseWrapper. saveOrUpdate really can either add or update an entity but this is only if the entity version is the same as the one of the detached object you are trying to add or merge.
In your particular example your detached object has a version previous to the last version in the database therefore the operation can not be executed on a stale data.
UPDATE:
MusicBrainzReleaseWrapper wrapper = session.get(release.getId()):
//the wrapper is managed object
if (wrapper == null) {
//initilize wrapper with the values from release
.......
session.save(wrapper)
}
else {
// do not set ID here. ID is aready present!!!
// never manuay set the version field here
wrapper.setName(release.getTitle().toLowerCase(Locale.UK));
wrapper.setXmldata(sw.toString());
session.saveOrUpdate(wrapper);
//In case you don't need update logic at all
// remove the #Version field from the entity
// and do othing in the else clause , or throw exception
// or log error or anything you see fit
}
No. saveOrUpdate method is used either to persist or merge an entity with the current session. It doesn't do what you expect. Either save or update entity is application's specific logic. Hibernate doesn't do any application's specific logic.
Session.merge() can directly save a previously unknown instance, but note it won't necessarily avoid the extra select against the database.
#Pavan is right about the entity being transient or detached in Hibernate (or JPA) terminology. Both of these states mean that Hibernate has not yet got a reference to this instance of the entity in its session (in the StatefulPersistenceContext), but detached clearly means it is known to the database.
merge() instructs Hibernate to stop and check for a detached instance. The first check is for the #Id value in the session, but if it's not already there, it must hit the database.
saveOrUpdate() instructs Hibernate that the caller knows it is safe to only check the StatefulPersistenceContext for the #Id. If it's not there, the entity is assumed to be transient (i.e. new), and Hibernate will proceed to the insert operation.
saveOrUpdate() is good for instances (with or without an #Id value) that are known to the session already.
In your case clearly Hibernate is unaware of the detached instance, so you would need to use merge(). But that also means Hibernate has to check the database for the instance it hasn't seen before - if the entity has an #Id value.
To come back to the original intent in your question, update without select is harder ...
For an update, Hibernate likes to know the prior state of the entity. This makes sense if it's using dynamic updates (so not updating all columns), but otherwise you would think it could go straight for the update. The only option I know of for this is a direct update query (via HQL or JPQL), but this is hardly convenient if you have an entity instance. Maybe someone else knows how to do this.
I'm using spring data jpa. I have a one to many relationship defined as so:
#OneToMany(mappedBy = "participant", cascade = CascadeType.ALL, orphanRemoval = true)
public List<SurveyResponse> getSurveyResponses() {
return surveyResponses;
}
I have a method that takes a collection of surveyResponses and replaces existing repsonses that have a matching key. Unfortunately, when I use it, hibernate deletes all responses other than the ones I'm adding. Why is that happening?
I never save responses directly, I just set them in the collection and let cascade do the rest. Here is the method that does the replacement:
public void updateResponses(List<SurveyResponse> responses) {
for (SurveyResponse response : responses) {
response.setParticipant(this);
}
if (this.surveyResponses != null) {
Set<String> questionKeys = responses.stream()
.map(SurveyResponse::getQuestionKey)
.collect(Collectors.toSet());
this.surveyResponses.removeIf(
surveyResponse -> questionKeys.contains(surveyResponse.getQuestionKey()));
this.surveyResponses.addAll(responses);
} else this.surveyResponses = responses;
}
After the addAll call the PersistentBag has a mix of old (managed) and new (not yet persisted) responses. When I call save (spring data jpa crudRepository method) on the participant entity, all old responses are deleted. Why?
This question should be deleted, as it was a bug elsewhere in my code. I made an overcomplicated setter that was causing the issue. Deleting questions with answers is discouraged so I'm just answering my own question here. For anyone's curiosity, this was the bad setter:
public void setSurveyResponses(List<SurveyResponse> surveyResponses) {
if (this.surveyResponses == null) {
this.surveyResponses = surveyResponses;
} else {
this.surveyResponses.clear();
if (surveyResponses != null) {
this.surveyResponses.addAll(surveyResponses);
}
}
if (this.surveyResponses != null) {
for (SurveyResponse response : this.surveyResponses) {
response.setParticipant(this);
}
}
}
Note to self, setters are for hibernate to use, not for your own code. If you need a fancy setter, make a new method.
This might be the cause of your problem:
http://assarconsulting.blogspot.com/2009/08/why-hibernate-does-delete-all-then-re.html
I would suggest replacing the collection type from List to Set and see how that behaves.
I have a Hibernate query with Criteria.
What I would like to do is (just for one query) tell hibernate to ignore an existing #ManyToOne annotation.
that is because hibernate creates and Left join on other tables.
I could figure it how to do it.
I have found this 2 links which didn't solve my problem:
Hibernate: How to remove an entity to which none refers to anymore in ManyToOne?
What is the difference between DELETE_ORPHAN and DELETE?
If you have such mapping:
//Parent
public class A {
...
}
//Child
public class B {
private A parent; //Many to one
...
}
Please try something like this:
Criteria q = ....;
q.setFetchMode("parent", FetchMode.SELECT);
....
I'm using Jersey and am expecitng a POST as an entity. However thst POST will also contain the UUID for one of its relationships:
Jersey Resource:
#POST
public WorkstationEntity save (WorkstationEntity workstationEntity) {
//WorkflowProcessEntity workflowProcessEntity = workflowProcessService.findByUUID();
workstationService.save(workstationEntity);
return workstationEntity;
}
How can I adjust the following mapping so it'll recognize the relationship and save correctly? Currently the workflow_process_id is NULL when it's saved and I have to query for the entity manually.
The JSON being posted is... {name: Workstation 1; workflow_process_id: 1}
private WorkflowProcessEntity workflowProcess;
#ManyToOne
#JoinColumn(name = "workflow_process_id", referencedColumnName = "id")
public WorkflowProcessEntity getWorkflowProcess() {
return workflowProcess;
}
public void setWorkflowProcess(WorkflowProcessEntity workflowProcess) {
this.workflowProcess = workflowProcess;
}
workstationService
#Transactional
public void save(WorkstationEntity workstationEntity) {
workstationRepository.save(workstationEntity);
}
Can you show code for workstationService? are you using Hibernatr or simple jdbc or any other orm tool?
I think inside workstationService.save(workstationEntity); you will need to attach workstationEntity to session (in case of Hibernate Hibernate Session). and then save it..
If I understand the problem it is that the returning json has a null id for the attached WorkstationProcessEntity id field. This is most likely a problem when you are trying to persist / merge the entity the transaction is not being committed before returning the detached entity. If you are using a persist make sure that you commit the transaction otherwise the id's will be null. Otherwise if you are using a merge this will commonly fix the problem.
protected T persist(T obj) {
EntityManager em = ThreadLocalPersistenceManager.getEntityManager();
EntityTransaction tx = em.getTransaction();
try {
if (! tx.isActive()) {
tx.begin();
}
em.persist(obj);
} finally {
if (!tx.getRollbackOnly()) {
tx.commit();
}
}
return obj;
}
The other likely cause is that your fetch is not set to eager so the datastore will only fetch the entity when it is accessed and by the time you are returning from the post the child entity is not attached. This is the most likely cause for your problem. What you should try is to access the workstation entitites getWorkflowProcess before closing the entity manager. Otherwise the attached entities will be null. Or add the FetchType.Eager annotation to fetch the child entities from the database when the parent is accessed.
My domain model in my Java EE 6 application contains bi-directional relationships like the following:
#Entity
public class Users implements PrimaryKeyHolder<String>, Serializable {
#Id
private String username;
#ManyToMany(mappedBy= "users")
private List<Category> categories;
public List<Category> getCategories() {
if (categories == null) {
categories = new ArrayList<Category>();
}
return Collections.unmodifiableList(categories);
}
public void addCategory(Category category) {
if (categories == null) {
categories = new ArrayList<Category>();
}
categories.add(category);
if (!category.getUsers().contains(this)) {
category.addUser(this);
}
}
public void removeCategory(Category category) {
if (categories == null) {
categories = new ArrayList<Category>();
}
categories.remove(category);
if (category.getUsers().contains(this)) {
category.removeUser(this);
}
}
public void setCategories(Collection<Category> categories) {
if (this.categories == null) {
this.categories = new ArrayList<Category>();
}
for (Iterator<Category> it = this.categories.iterator(); it.hasNext();) {
Category category = it.next();
it.remove();
if (category.getUsers().contains(this)) {
category.removeUser(this);
}
}
for (Category category : categories) {
addCategory(category);
}
}
}
#Entity
public class Category implements PrimaryKeyHolder<Long>, Serializable {
#Id
private Long id;
#ManyToMany
private List<User> users;
public List<User> getUsers() {
if (users == null) {
users = new ArrayList<User>();
}
return Collections.unmodifiableList(users);
}
protected void addUser(User user) {
if (users == null) {
users = new ArrayList<User>();
}
users.add(user);
}
protected void removeUser(User user) {
if (users == null) {
users = new ArrayList<User>();
}
users.remove(user);
}
}
UPDATE: I added relationship management code. Relationships are only set on the user side, therefore, the add/remove methods are protected in the Categoriy class. I set the categories on the user via setCategories.
Eclipselink correctly generates a join table CATEGORY_USERS. However, it does not persist any information in it (it only caches the information). E.g. when I execute a find operation on the entity manager (e.g. a user), it returns the complete object graph (including the category relationship). But when I look at the tables, information are not updated (even though the transactions are committed). I also inserted a flush operation in my code, without success. Basic information (like String, Integer, etc. columns) gets correctly persisted and updated. After turning the log level to FINE, I can see that no SQL statements are executed for the relationships and the join table, respectively. But I do see SQL statements for uni-directional relationships.
My datamodel is covered by extensive unit tests, which all pass successfully. I basically do the same operation as in the container, commit the transaction, reload the entities from the db and check if the relationships are correctly set, which they are (I'm using the in-memory derby database for testing).
My app server is Glassfish v3.1-b17.
Any help is appreciated.
Thanks,
Theo
Ensure you are setting both sides of the relationship. The specification requires that the application sets both sides of the relationship as there is no relationship maintenance in JPA.
After endless hours of trying I finally got to a solution: I simply changed the owning side of the relationship, i.e. I put the mappedBy attribute to the category entity like this:
#ManyToMany(mappedBy= "categories")
private List<User> users;
The explanation for this can be found here
Four points:
1.- When you have an error, it's more simple find solution isolating them in an example (Or unit test) that reproduces the error. In your case, you could do an example with more simple getter and setter (for example, removing unmodifiableList use and other innecesary methods for testing actually issue).
2.- I advise you to use pojos for model, without any logic. So, remove logic from pojos.
3.- We are using eclipselink and we do not have problems persisting relations. So, it is more possible that error will be in your code.
4.- Test annoting relation with "cascade = javax.persistence.CascadeType.ALL"
Apology for my poor English :(