CRUDRepository unable to save modified entities - spring

I'm trying to fetch some data from the database, update a field with some other entity and save it back to the DB, of course I've made sure that both the first entity and the entity that is going to be inserted are retrieved and fine, it is just thrown upon the save function invokation.
Here's the exception
[err] org.springframework.dao.DataIntegrityViolationException: Attempt to persist detached object "repository.entities.RequestEntity-0". If this is a new instance, make sure any version and/or auto-generated primary key fields are null/default when persisting.; nested exception is <openjpa-2.4.3-r422266:1833086 nonfatal store error> org.apache.openjpa.persistence.EntityExistsException: Attempt to persist detached object "repository.entities.RequestEntity-0". If this is a new instance, make sure any version and/or auto-generated primary key fields are null/default when persisting.
FailedObject: repository.entities.RequestEntity-0
The entity
#Entity
#Table(name="REQUEST")
public class RequestEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="REQUEST_ID")
private long requestId;
some other fields ....
//bi-directional many-to-one association to MStatus
#ManyToOne
#JoinColumn(name="STATUS")
private MStatus mStatus;
getters and setters here as well ..
}
And lastly, here's the code
private void doStuff() throws Exception {
List<RequestEntity> requestsList = requestRepo
.findByMStatusStatusContaining("TEXT");
RequestEntity requestItem;
if (requestsList.size() > 1 || requestsList.isEmpty()) {
throw new Exception("No requests found");
} else {
requestItem = requestsList.get(0);
}
requestItem.setMApprovalStatus(mapprovalStatus.findOne("TEXT_TWO"));
requestRepo.save(requestItem);
}

Related

Saving Entity with Cached object in it causing Detached Entity Exception

I'm trying to save an Entity in DB using Spring Data/Crud Repository(.save) that has in it another entity that was loaded through a #Cache method. In other words, I am trying to save an Ad Entity that has Attributes entities in it, and those attributes were loaded using Spring #Cache.
Because of that, I'm having a Detached Entity Passed to Persist Exception.
My question is, is there a way to save the entity still using #Cache for the Attributes?
I looked that up but couldn't find any people doing the same, specially knowing that I am using CrudRepository that has only the method .save(), that as far as I know manages Persist, Update, Merge, etc.
Any help is very much appreciated.
Thanks in advance.
Ad.java
#Entity
#DynamicInsert
#DynamicUpdate
#Table(name = "ad")
public class Ad implements SearchableAdDefinition {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private User user;
#OneToMany(mappedBy = "ad", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<AdAttribute> adAttributes;
(.....) }
AdAttribute.java
#Entity
#Table(name = "attrib_ad")
#IdClass(CompositeAdAttributePk.class)
public class AdAttribute {
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ad_id")
private Ad ad;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "attrib_id")
private Attribute attribute;
#Column(name = "value", length = 75)
private String value;
public Ad getAd() {
return ad;
}
public void setAd(Ad ad) {
this.ad = ad;
}
public Attribute getAttribute() {
return attribute;
}
public void setAttribute(Attribute attribute) {
this.attribute = attribute;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
#Embeddable
class CompositeAdAttributePk implements Serializable {
private Ad ad;
private Attribute attribute;
public CompositeAdAttributePk() {
}
public CompositeAdAttributePk(Ad ad, Attribute attribute) {
this.ad = ad;
this.attribute = attribute;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CompositeAdAttributePk compositeAdAttributePk = (CompositeAdAttributePk) o;
return ad.getId().equals(compositeAdAttributePk.ad.getId()) && attribute.getId().equals(compositeAdAttributePk.attribute.getId());
}
#Override
public int hashCode() {
return Objects.hash(ad.getId(), attribute.getId());
}
}
Method using to load Attributes:
#Cacheable(value = "requiredAttributePerCategory", key = "#category.id")
public List<CategoryAttribute> findRequiredCategoryAttributesByCategory(Category category) {
return categoryAttributeRepository.findCategoryAttributesByCategoryAndAttribute_Required(category, 1);
}
Method used to create/persist the Ad:
#Transactional
public Ad create(String title, User user, Category category, AdStatus status, String description, String url, Double price, AdPriceType priceType, Integer photoCount, Double minimumBid, Integer options, Importer importer, Set<AdAttribute> adAtributes) {
//Assert.notNull(title, "Ad title must not be null");
Ad ad = adCreationService.createAd(title, user, category, status, description, url, price, priceType, photoCount, minimumBid, options, importer, adAtributes);
for (AdAttribute adAttribute : ad.getAdAttributes()) {
adAttribute.setAd(ad);
/* If I add this here, I don't face any exception, but then I don't take benefit from using cache:
Attribute attribute = attributeRepository.findById(adAttribute.getAttribute().getId()).get();
adAttribute.setAttribute(attribute);
*/
}
ad = adRepository.save(ad);
solrAdDocumentRepository.save(AdDocument.adDocumentBuilder(ad));
return ad;
}
I don't know if you still require this answer or not, since it's a long time, you asked this question. Yet i am going to leave my comments here, someone else might get help from it.
Lets assume, You called your findRequiredCategoryAttributesByCategory method, from other part of your application. Spring will first check at cache, and will find nothing. Then it will try to fetch it from Database. So it will create an hibernate session, open a transaction, fetch the data, close the transaction and session. Finally after returning from the function, it will store the result set in cache for future use.
You have to keep in mind, those values, currently in the cache, they are fetched using a hibernate session, which is now closed. So they are not related to any session, and now at detached state.
Now, you are trying to save and Ad entity. For this, spring created a new hibernate session, and Ad entity is attached to this particular session. But the attributes object, that you fetched from the Cache are detached. That's why, while you are trying to persist Ad entity, you are getting Detached Entity Exception
To resolve this issue, you need to re attach those objects to current hibernate session.I use merge() method to do so.
From hibernate documentation here https://docs.jboss.org/hibernate/orm/3.5/javadocs/org/hibernate/Session.html
Copy the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. Return the persistent instance. If the given instance is unsaved, save a copy of and return it as a newly persistent instance. The given instance does not become associated with the session. This operation cascades to associated instances if the association is mapped with cascade="merge".
Simply put, this will attach your object to hibernate session.
What you should do, after calling your findRequiredCategoryAttributesByCategory method, write something like
List attributesFromCache = someService.findRequiredCategoryAttributesByCategory();
List attributesAttached = entityManager.merge( attributesFromCache );
Now set attributesAttached to your Ad object. This won't throw exception as attributes list is now part of current Hibernate session.

Persisting composite pojo in hibernate but inner pojo getting freshly persisted

The situation is : I have a Pojo DEVICE, which has a pojo Namespace pojo inside. :
#Entity
#Table(name = "device")
public class Device implements java.io.Serializable {
private long deviceId;
private long timestamp;
private NamespaceMaster namespaceMaster;
..................
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "namespace_id")
#Cascade(value = CascadeType.ALL)
public NamespaceMaster getNamespaceMaster() {
return this.namespaceMaster;
}
When i am persisting Device, I am querying database to find the appropriate namespace and then setting the namespace pojo to device. Up to this part there is no isse.
NamespaceMaster namespaceMaster=null;
try {
namespaceMaster = namespaceDAOImpl.queryAllByName(namespace, AZURE_CLOUDTYPE).get(0);
} catch (DaoException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
LOG.info("Namespace object from cache :{}",namespaceMaster);
device.setNamespaceMaster(namespaceMaster);
Now when am trying persist Device, the namespace table in DB also has new row inserted with the same value that I initially queried namespace table. This is happening due to
#Cascade(value = CascadeType.ALL)
The reason is : Hibernate is unable to recognise the namespace object as already persisted one. So it cascades all the pojo inside and hense inserts a new one.
My question is simple. How to insert device object in table such that the namespace is not generated new. It is the one that is already present in namespace table. I tried removing #Cascade(value = CascadeType.ALL), but then i get a error :
object references an unsaved transient instance - save the transient instance before flushing
Note
I have added #version in NamespaceMaster table
#Version
private Long version;
private static final long serialVersionUID = 1L;
I am struck guys!! Please suggest.
Thanks in advance.
Solved the above issue.
I had inserted namespace_id(primary key for namespace table) as 0 for my data. May be hibernate is unable to understand 0 as the key and resolve in this situation. Guys be aware of this fact!!
I tried giving implementation for hashcode(),equals;#Version etc but nothing worked. updating my table primary key from 0 to a higher value worked!!
Anyways!!

JPA Hibernate Spring Repository ensures transaction completes on save?

I am creating a simple spring application which is supposed to book seats in a seminar. Lets say Booking class looks like this
#Entity
#Table(name = "bookings")
#IdClass(BookingId.class)
public class Booking{
#Id
private Long seminarId;
#Id
private String seatNo;
// .. other fields like perticipant info
// .. getter setters
}
of course the BookingId class:
public class BookingId implements Serializable{
private static final long serialVersionUID = 1L;
private Long seminarId;
private String seatNo;
// .. constructors, getters, setters
}
And I have a repository
#Repository
public interface BookingsRepository extends JpaRepository<Booking, BookingId>{
}
in the controller when a booking request arrives I first check if a booking with same seminer id and seat number already exists, if it doesn't exist I create one
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<BaseCrudResponse> createNewBooking(#Valid #RequestBody NewBookingDao newBookingDao, BindingResult bindingResult){
logger.debug("Request for a new booking");
// .. some other stuffs
Booking newBooking = new Booking();
newBooking.setSeminarId(newBookingDao.getSeminarId());
newBooking.setSeatNumber(newBookingDao.getSeatNumber());
// .. set other fields
Booking existing = bookingsRepository.findOne(new BookingId(newBooking.getSeminarId(), newBooking.getSeatNumber());
if (existing == null)
bookingsRepository.save(newBooking);
return new ResponseEntity<>(new BaseCrudResponse(0), HttpStatus.CREATED);
}
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
Now what will happen if the save method of the repository didn't finish commiting transaction and another request already gets past the existence check ? There might be incorrect booking (the last commit will override the previous). Is this scenario likely to happen ? Will the repository ensures that it completes the transaction before another save call ?
Also is there any way to tell Jpa to throw some exception (for IntegrityConstraintException if the composite key (in this case seminerId and seatNumber) already exists ? Now in the present setting its just updating the row.
You can use javax.persistence.LockModeType.PESSIMISTIC_WRITE so other transactions except the one that got the lock cannot update the entity.
If you use spring-data > 1.6 you can annotate the repository method with #Lock :
interface BookingsRepository extends Repository<Booking, Long> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
Booking findOne(Long id);
}
For sure you need to handle the locking exception that may be thron in the controller.

OneToMany Create Fails with InvalidDataAccessApiUsageException

I am fairly new to Hibernate and have been using the manual & online forums, but I am stumped on this issue. I’m using Spring 3.2 with Hibernate 4 & Annotations. I have a parent (PledgeForm) & child (PledgeFormGiftLevel) table that is one-to-many.
Domain/Models:
Parent
#Entity
#Table(name="PLEDGE_FORMS")
#SuppressWarnings("serial")
public class PledgeForm implements Serializable {
static final Logger log = Logger.getLogger(PledgeForm.class);
#Id
#GeneratedValue(strategy=GenerationType.AUTO, generator="pledge_form_seq")
#SequenceGenerator(name="pledge_form_seq", sequenceName="PLEDGE_FORM_SEQ")
#Column(name="ID", unique=true, nullable=false)
private Integer id;
….
#OneToMany(mappedBy="pledgeForm", fetch=FetchType.EAGER, cascade=CascadeType.ALL)//********1
private List<PledgeFormGiftLevel> pledgeFormGiftLevels = new ArrayList<PledgeFormGiftLevel>();
….
public List<PledgeFormGiftLevel> getPledgeFormGiftLevels() {
return this.pledgeFormGiftLevels;
}
public void setPledgeFormGiftLevels(List<PledgeFormGiftLevel> pledgeFormGiftLevels) {
this.pledgeFormGiftLevels = pledgeFormGiftLevels;
}
//I do not think the following method is needed, but I decided to try it just in case
public void addPledgeFormGiftLevels(PledgeFormGiftLevel pledgeFormGiftLevels) {
pledgeFormGiftLevels.setPledgeForm(this);
getPledgeFormGiftLevels().add(pledgeFormGiftLevels);
}
Child
#Entity
#Table(name="PLEDGE_FORM_GIFT_LEVELS")
#SequenceGenerator(name="pledge_form_gift_level_seq", sequenceName="PLEDGE_FORM_GIFT_LEVEL_SEQ")
#SuppressWarnings("serial")
public class PledgeFormGiftLevel implements Serializable {
static final Logger log = Logger.getLogger(PledgeFormGiftLevel.class);
#Id
#GeneratedValue(strategy=GenerationType.AUTO, generator="pledge_form_gift_level_seq")
#Column(name="ID", unique=true, nullable=false)
private Integer id;
…
#ManyToOne(fetch=FetchType.EAGER)//yes?
#JoinColumn(name="PLEDGE_FORM_ID", referencedColumnName="ID", insertable=true, updatable=true)//yes?
private PledgeForm pledgeForm = new PledgeForm();
…
public PledgeForm getPledgeForm() {
return pledgeForm;
}
public void setPledgeForm(PledgeForm pledgeForm) {
this.pledgeForm = pledgeForm;
}
Controller (there is a graphic, so I have code to pull in the file):
#Controller
#SessionAttributes("pledgeForm")
public class PledgeFormController {
#Autowired
org.unctv.service.PledgeFormManager Service;
…
#RequestMapping(value = "/saveJdbcPledgeForm", method = RequestMethod.POST, params="save")
public ModelAndView save(
#ModelAttribute("pledgeForm")
#Valid PledgeForm pledgeForm, BindingResult result,
#RequestParam("logoImg") MultipartFile file,
#RequestParam(value="removeLogoImg", required=false) String removeLogoImg) throws Exception {
ModelAndView mav = null;
mav = new ModelAndView("pledgeFormSearch");//Name of the JSP
if (removeLogoImg != null) {
pledgeForm.setLogoFilename(null);
pledgeForm.setLogoImg(null);
pledgeForm.setLogoContentType(null);
} else if (file != null && file.getBytes().length > 0) {
pledgeForm.setLogoFilename(file.getOriginalFilename());
pledgeForm.setLogoImg(file.getBytes());
pledgeForm.setLogoContentType(file.getContentType());
}
Service.save(pledgeForm);
mav.addObject("pledgeForm", pledgeForm);//JSP Form's Command Name (pledgeForm);
mav.addObject("cmdName", "pledgeForm");
mav.addObject("actionType", "Save");
return mav;
}
Service:
#Service("simplePledgeFormManager")
#Transactional(readOnly=true)
public class SimplePledgeFormManager implements PledgeFormManager {
#Autowired
private HibernatePledgeFormDao hibernatePledgeFormDao;
…
#Transactional(readOnly=false)
public void save(PledgeForm pledgeForm) throws Exception {
hibernatePledgeFormDao.save(pledgeForm);
}
DAO:
#Repository("PledgeFormDAO")
public class HibernatePledgeFormDao implements PledgeFormDao {
static final Logger log = Logger.getLogger(HibernatePledgeFormDao.class);
#Autowired
private SessionFactory sessionFactory;
...
#Override
public void save(PledgeForm pledgeForm) throws Exception {
sessionFactory.getCurrentSession().saveOrUpdate(pledgeForm);
}
Using the code above, parent/child records can be selected and updated fine. When I display the “trace” messages from hibernate, the update does have this trace message about the child, though:
[2013-12-06 10:31:24,648] TRACE Persistent instance of: org.unctv.domainmodel.PledgeFormGiftLevel
[2013-12-06 10:31:24,649] TRACE Ignoring persistent instance
[2013-12-06 10:31:24,649] TRACE Object already associated with session: [org.unctv.domainmodel.PledgeFormGiftLevel#1]
The create always gives this error if there is a child record:
object references an unsaved transient instance - save the transient instance before flushing: org.unctv.domainmodel.PledgeForm; nested exception is org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: org.unctv.domainmodel.PledgeForm
When I look at the hibernate logs, I see that it updates the parent & the child based on transient objects. Then it tries to flush & finds a persistent copy of the child, so it rolls back everything.
[2013-12-06 10:34:13,615] TRACE Automatically flushing session
[2013-12-06 10:34:13,615] TRACE Flushing session
[2013-12-06 10:34:13,615] DEBUG Processing flush-time cascades
[2013-12-06 10:34:13,615] TRACE Processing cascade ACTION_SAVE_UPDATE for: org.unctv.domainmodel.PledgeForm
[2013-12-06 10:34:13,615] TRACE Cascade ACTION_SAVE_UPDATE for collection: org.unctv.domainmodel.PledgeForm.pledgeFormGiftLevels
[2013-12-06 10:34:13,615] TRACE Cascading to save or update: org.unctv.domainmodel.PledgeFormGiftLevel
[2013-12-06 10:34:13,616] TRACE Persistent instance of: org.unctv.domainmodel.PledgeFormGiftLevel
[2013-12-06 10:34:13,616] TRACE Ignoring persistent instance
[2013-12-06 10:34:13,616] TRACE Object already associated with session: [org.unctv.domainmodel.PledgeFormGiftLevel#51]
[2013-12-06 10:34:13,616] TRACE Done cascade ACTION_SAVE_UPDATE for collection: org.unctv.domainmodel.PledgeForm.pledgeFormGiftLevels
[2013-12-06 10:34:13,616] TRACE Done processing cascade ACTION_SAVE_UPDATE for: org.unctv.domainmodel.PledgeForm
[2013-12-06 10:34:13,617] DEBUG Dirty checking collections
[2013-12-06 10:34:13,617] TRACE Flushing entities and processing referenced collections
[2013-12-06 10:34:13,617] DEBUG Collection found: [org.unctv.domainmodel.PledgeForm.pledgeFormGiftLevels#51], was: [<unreferenced>] (initialized)
[2013-12-06 10:34:13,618] DEBUG rolling back
[2013-12-06 10:34:13,618] DEBUG rolled JDBC Connection
The Hibernate documentation shows this as even simpler than I my code is, but I had to add the fetch & cascade values. I’ve played with changing the fetch & cascade values & placement (starting with the Hibernate documentation & then adding on), but everything else I try still causes the create to fail & often causes the update to fail too.
Many forum posts that I find show flush() or evict(). I am not certain if it is Hibernate 4 or annotations (#Transactional, I think) I’m using, but I do not see a place for that in my code. From the Hibernate trace logs, I can see that flushing is occurring automatically with in the saveOrUpdate() method.
I also tried dropping the tables & sequences & starting fresh.
Any advice about getting the create to work is appreciated. If you can point me to specific documentation that I missed, that is appreciated as well.
Thanks,
Bonnie
I noticed that equals and hashcode have not been overridden in the entities. These methods are used to compare objects to determine their equality. Hibernate may not be able to determine if an existing instance of the entity exists without these methods being overridden. Try providing implementations for hashcode and equals.
If your using Eclipse, press CTRL + SHIFT + S, H to bring up the dialog for creating the hashcode and equals methods. Pick fields that contain values that are relatively unchanged and then generate the methods.
Also be sure that you are managing both sides of the entity as discussed in the above comments:
public ModelAndView save(
#ModelAttribute("pledgeForm")
#Valid PledgeForm pledgeForm, BindingResult result,
#RequestParam("logoImg") MultipartFile file,
#RequestParam(value="removeLogoImg", required=false) String removeLogoImg) throws Exception {
ModelAndView mav = null;
mav = new ModelAndView("pledgeFormSearch");//Name of the JSP
//Manage both sides of the entity
List<PledgeFormGiftLevel> levels = pledgeForm.getPledgeFormGiftLevels();
for(PledgeFormGiftLevel level: levels){
level.setPledgeForm(pledgeForm);
}
if (removeLogoImg != null) {
pledgeForm.setLogoFilename(null);
pledgeForm.setLogoImg(null);
pledgeForm.setLogoContentType(null);
} else if (file != null && file.getBytes().length > 0) {
pledgeForm.setLogoFilename(file.getOriginalFilename());
pledgeForm.setLogoImg(file.getBytes());
pledgeForm.setLogoContentType(file.getContentType());
}
Service.save(pledgeForm);
mav.addObject("pledgeForm", pledgeForm);//JSP Form's Command Name (pledgeForm);
mav.addObject("cmdName", "pledgeForm");
mav.addObject("actionType", "Save");
return mav;
}

JPA unable to assign a new persisted entity in a many to one relationship

I have to JPA Entities defined with a bidirectional relationship many to one, hereby:
#Entity
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="DEPARTAMENTO_ID_GENERATOR",sequenceName="DEPARTAMENTO_SEQ")
#GeneratedValue(strategy=GenerationType.SEQUENCE,generator="DEPARTAMENTO_ID_GENERATOR")
#Column(name="DEP_ID")
private long id;
#Column(name="DEP_DESC")
private String desc;
//bi-directional many-to-one association to Academico
#OneToMany(mappedBy="department")
private Set<Proffesor> proffesors;
//getters and setters
}
#Entity
#Table(name="ACADEMICOS")
public class Proffesor implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="ACADEMICOS_ID_GENERATOR", sequenceName="ACADEMICOS_SEQ")
#GeneratedValue(strategy=GenerationType.SEQUENCE,generator="ACADEMICOS_ID_GENERATOR")
#Column(name="ACD_ID")
private long id;
#ManyToOne(cascade={CascadeType.PERSIST,CascadeType.MERGE})
#JoinColumn(name="ACD_DEPADSCRITO_DEP")
private Department department;
// getters and setters.
}
After in a transactional Spring service I have the next code to manipulate the entities in this way.
#Transactional (propagation=Propagation.REQUIRED)
public void createDepartmentWithExistentProffesor(String desc,Long idAvaiableProf) {
// new department
Department dep = new Department();
dep.setDesc(desc);
HashSet<Proffesor> proffesors = new HashSet<Proffesor>();
dep.setProffesors(proffesors);
// I obtain the correct attached Proffesor entity
Proffesor proffesor=DAOQueryBasic.getProffesorById(idAvaiableProf);
// I asign the relationship beetwen proffesor and department in both directions
dep.addProffesors(proffesor);
// Persists department
DAODataBasic.insertDepartment(dep);
// The id value is not correct then Exception ORA-0221
System.out.println("SERVICIO: Departamento creado con id: " + dep.getId());
}
As I said in the comments the id of the new Department persisted is not a real database id inside the transaction, then it is produced an exception
Exception in thread "main" org.springframework.orm.jpa.JpaSystemException: org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
........
Caused by: java.sql.BatchUpdateException: ORA-02291: integrity restiction (HIBERNATE_PRB.FK_ACD2DEP) violated - primary key don't found
I've tried in a test, persist the new departmen entity with no relationship with Proffesor and I've seen that the id of the new department persisted entity has not a valid value inside the transaction but out of the transaction already the id has a correct value.
But I need the correct value inside the transaction.
Can anybody help me?
Thank you in advance.
try this
#OneToMany(mappedBy="department",cascade = CascadeType.PERSIST)
private Set<Proffesor> proffesors;

Resources