Hi I am beginner on the JPA world, I have a question on the auto-generated id. We are using OpenJPA, My application requires that one operation which creates bunch of related objects must be inside a single transaction which will be part of global transaction (XA). I am struggling in get the auto-generated id and use it to set values in other object. Here is the snapshot:
#ENTITY
#Table(name="TDepart")
class Department{
private long id;
#GeneratedValue(strategy= GenerationType.TABLE)
public long getId();
}
//And some classes like
class Professor {
void setDepartmentId(long id);
}
Now I have a business operation:
void doSomething()
{
Department depart = new Department();
handleProfessors (depart);
handleStudent (depart);
//and someother rountines need to refer department
}
//sample code which will getId
void handleProfessors(Department depart)
{
Professor p = new Professor ();
p.setDepartmentId(depart.getId);
}
So the Department.getId() will be called several times. The doSomething() will be in a single managed transaction, but the GeneratedValue will use an unmanaged tx. Now may problem is: whenever the getId is called, it will return a new value, and when the department is final persisted, the id is the latest number, so all other objects refer to an non-exists department. Is there anyway to handle this so that the id is (kindof) persist?
I have a loose requirement solution, which will create an dummy department first and persist it, so the ID is not change. The code is similar to this:
void doSomething()
{
Department depart = createEmptyDepartment(); // always new tx so department is created;
try {
reallyDoSomehing(); // tx required so it is part of global tx
}
catch (SomeException e) {
removeEmptyDepartment(depart);
}
Now I do not know how I can set the tx for removeEmptyDepartment(), if is required it will use the global request so it will be rollback as well. If it is new tx it will cause a deadlock since reallyDoSomething() will lock the db row.
Please, give me some ideas on how to solve it.
Thanks,
Howard.
I don't fully understand your issue, but I'm thinking that rather than setting the departmentId in your professor class, you should be setting the Department instead
i.e.
void setDepartmentId(long id);
change to
void setDepartment(Department d);
The id components should be handled automatically by the entity manager
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.
So I have looked at various tutorials about JPA with Spring Data and this has been done different on many occasions and I am no quite sure what the correct approach is.
Assume there is the follwing entity:
package stackoverflowTest.dao;
import javax.persistence.*;
#Entity
#Table(name = "customers")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
public Customer(String name) {
this.name = name;
}
public Customer() {
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
We also have a DTO which is retrieved in the service layer and then handed to the controller/client side.
package stackoverflowTest.dto;
public class CustomerDto {
private long id;
private String name;
public CustomerDto(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
So now assume the Customer wants to change his name in the webui - then there will be some controller action, where there will be the updated DTO with the old ID and the new name.
Now I have to save this updated DTO to the database.
Unluckily currently there is no way to update an existing customer (except than deleting the entry in the DB and creating a new Cusomter with a new auto-generated id)
However as this is not feasible (especially considering such an entity could have hundreds of relations potentially) - so there come 2 straight forward solutions to my mind:
make a setter for the id in the Customer class - and thus allow setting of the id and then save the Customer object via the corresponding repository.
or
add the id field to the constructor and whenever you want to update a customer you always create a new object with the old id, but the new values for the other fields (in this case only the name)
So my question is wether there is a general rule how to do this?
Any maybe what the drawbacks of the 2 methods I explained are?
Even better then #Tanjim Rahman answer you can using Spring Data JPA use the method T getOne(ID id)
Customer customerToUpdate = customerRepository.getOne(id);
customerToUpdate.setName(customerDto.getName);
customerRepository.save(customerToUpdate);
Is's better because getOne(ID id) gets you only a reference (proxy) object and does not fetch it from the DB. On this reference you can set what you want and on save() it will do just an SQL UPDATE statement like you expect it. In comparsion when you call find() like in #Tanjim Rahmans answer spring data JPA will do an SQL SELECT to physically fetch the entity from the DB, which you dont need, when you are just updating.
In Spring Data you simply define an update query if you have the ID
#Repository
public interface CustomerRepository extends JpaRepository<Customer , Long> {
#Query("update Customer c set c.name = :name WHERE c.id = :customerId")
void setCustomerName(#Param("customerId") Long id, #Param("name") String name);
}
Some solutions claim to use Spring data and do JPA oldschool (even in a manner with lost updates) instead.
Simple JPA update..
Customer customer = em.find(id, Customer.class); //Consider em as JPA EntityManager
customer.setName(customerDto.getName);
em.merge(customer);
This is more an object initialzation question more than a jpa question, both methods work and you can have both of them at the same time , usually if the data member value is ready before the instantiation you use the constructor parameters, if this value could be updated after the instantiation you should have a setter.
If you need to work with DTOs rather than entities directly then you should retrieve the existing Customer instance and map the updated fields from the DTO to that.
Customer entity = //load from DB
//map fields from DTO to entity
So now assume the Customer wants to change his name in the webui -
then there will be some controller action, where there will be the
updated DTO with the old ID and the new name.
Normally, you have the following workflow:
User requests his data from server and obtains them in UI;
User corrects his data and sends it back to server with already present ID;
On server you obtain DTO with updated data by user, find it in DB by ID (otherwise throw exception) and transform DTO -> Entity with all given data, foreign keys, etc...
Then you just merge it, or if using Spring Data invoke save(), which in turn will merge it (see this thread);
P.S. This operation will inevitably issue 2 queries: select and update. Again, 2 queries, even if you wanna update a single field. However, if you utilize Hibernate's proprietary #DynamicUpdate annotation on top of entity class, it will help you not to include into update statement all the fields, but only those that actually changed.
P.S. If you do not wanna pay for first select statement and prefer to use Spring Data's #Modifying query, be prepared to lose L2C cache region related to modifiable entity; even worse situation with native update queries (see this thread) and also of course be prepared to write those queries manually, test them and support them in the future.
I have encountered this issue!
Luckily, I determine 2 ways and understand some things but the rest is not clear.
Hope someone discuss or support if you know.
Use RepositoryExtendJPA.save(entity). Example:
List<Person> person = this.PersonRepository.findById(0)
person.setName("Neo");
This.PersonReository.save(person);
this block code updated new name for record which has id = 0;
Use #Transactional from javax or spring framework. Let put #Transactional upon your class or specified function, both are ok. I read at somewhere that this annotation do a "commit" action at the end your function flow. So, every things you modified at entity would be updated to database.
There is a method in JpaRepository
getOne
It is deprecated at the moment in favor of
getById
So correct approach would be
Customer customerToUpdate = customerRepository.getById(id);
customerToUpdate.setName(customerDto.getName);
customerRepository.save(customerToUpdate);
I faced with a very strange behavior in my web app with spring 3 and hibernate-core 3.5.1-Final.
For simplicity i provide my code..
if(ripid!=null){ //Parameter
Appuntamento apDaRip = appuntamentoService.findById(ripid);
if(apDaRip.getIdpadre()!=null){
apDaRip.setNota("RIPROGRAMMATO n."+ripid.toString()+"\n"+apDaRip.getNota());
apDaRip.setIdpadre(apDaRip.getIdpadre());
}else{
apDaRip.setNota("RIPROGRAMMATO n."+ripid.toString()+"\n"+apDaRip.getNota());
apDaRip.setIdpadre(ripid);
}
try{
apDaRip.setOrarioinizio(null);
apDaRip.setDurata(null);
//apDaRip.setIdappuntamento(null);
}catch(Exception e){e.printStackTrace();}
map.put("appuntamento", apDaRip);
}
di = datiintranetService.findById(DatiintranetService.PASS_X_INTERVENTI);
map.put("passinterventi", di.getBoolean());
The idea behind is to use some data of an object "Appuntamento" for produce a new one.
So i'm going to change some value and before send the object to my view (jsp) i fetch other data by calling findbyid. This cause an update to the Appuntamento object... Off course i don't want this behavior. Someone can have an explanation of this?
Edit-1
Here's the Dao
#Transactional
public class DatiintranetService {
private DatiintranetDAO datiintranetDAO;
public void setDatiintranetDAO(DatiintranetDAO datiintranetDAO) {
this.datiintranetDAO = datiintranetDAO;
}
public DatiintranetDAO getDatiintranetDAO() {
return datiintranetDAO;
}
public Datiintranet findById(Integer id) {
return datiintranetDAO.findById(id);
}
}
and For Appuntamento class I provide to you a snapshot
#Entity
#Table(name = "appuntamento", schema = "public")
public class Appuntamento implements java.io.Serializable {
#Id
#SequenceGenerator(name="appuntamentoID", sequenceName="appuntamento_idappuntamento_seq",allocationSize =1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="appuntamentoID")
#Column(name = "idappuntamento", unique = true, nullable = false)
public Integer getIdappuntamento() {
return this.idappuntamento;
}
}
Edit-2
IF i move thoese two row above the if statement no update occur.
di = datiintranetService.findById(DatiintranetService.PASS_X_INTERVENTI);
map.put("passinterventi", di.getBoolean());
If you query for an entity and change the entity, the default behavior is to persist those changes via an update to the database. This is usually what you want to happen, but obviously not in all cases.
If you want to avoid the update, you need to detach the entity by calling session.evict(apDaRip) where session is a reference to the hibernate session (see Session.evict()). You probably want to evict the entity right after you get it (immediately following the call to findById).
In an EJB-class i have two methods with remote interface:
Class MyBean {
public CustomerEntity getCustomer(String id) {......}
public void updateCustomer(CustomerEntity newValues, CustomerEntity oldValues) {......}
}
Customer Entity consist of some fields with getters and setters.
#Entity
public class Customer {
#ID private String id;
#Column private String name;
#Column private String phone;
// Getters and setters
.
.
}
The client app does:
Customer customer myBeanRemoteInterface.getCustomer("some id");
Customer oldCustomer = customer; //Save original customer data
displayCustomerFormAndAcceptChanges(customer);
myBeanRemoteInterface.updateCustomer(customer, oldCustomer);
The EJB updateCustomer should now update the customer on the server. To avoid write over any changes made to other fields by other users, only fields that the user has changed should be commited. Like the following:
public void updateCustomer(CustomerEntity newValues, CustomerEntity oldValues) {
Customer customer = entityManager.find(Customer.class, oldValues.getId());
if (!newValues.getName().equals(oldValues.getName()) { // Value updated
// If the value fetched by entityManager.find is different from what was originally fetched that indicates that the value has been updated by another user.
if (!customer.getName().equals(oldValues.getName()) throw new CustomerUpdatedByOtherUserException();
else customer.setName(newValues.getName());
}
// repeat the code block for every field in Customer class
entityManager.flush();
}
The problem now is that the code block in updateCustomer needs to be repeated once for every field in the Customer class. If new fields is inserted into the Customer class, also the EJB needs to be updated.
I need a solution that works without any need for updateing the EJB if more fields is added to Customer class.
Any suggestions?
Use Java Reflection:
Method[] methods = Customer.class.getDeclaredMethods();
Class<?>[] methodParams = null;
Object[] paramValue = new Object[1];
for (Method method : methods) {
if(method.getName().contains("set")) //This is for set methods.
{
methodParams = method.getParameterTypes();
if(methodParams[0].equals(String.class))
{
paramValue[0] = "some string"; // Assigning some value to method parameter
}
method.invoke(customer, paramValues); // customer is your object you are executing your methods on.
}
}
You should really consider adding a #Version annotated field to your Entity to have your JPA implementation take care of Optimistic Locking and then deal with situations where you try to update using "stale" data.
Otherwise you're jeopardizing your data-integrity.
Cheers //Lutz
Let's say I had this in my UserDAO class:
#OneToMany(cascade=CascadeType.ALL, mappedBy="owner", fetch=FetchType.EAGER, orphanRemoval=true)
private Set<Vehicle> vehicles = new HashSet<Vehicle>();
Is this the recommended way to add a new vehicle to a User:
User user = userService.findByLoginName("MartinL");
Vehicle newVehicle = new Vehicle();
newVehicle.set(...) // setters omitted
newVehicle.setOwner(user) // is this needed in any case?
user.getVehicles().add(newVehicle) // add the new vehicle to the Set in User class
userService.save(user); // persist the modified user object to database
Is this the best practice or do I miss on anything?
I prefer the direct approach of saving the newVehicle (after setting the owner attribute, of course).
Advantages:
1) Your approach will force the framework to check all the attributes of user and write them back at the DB, and will also write again all of the vehicles.
2) It is more readable, in the sense that it makes more clear that your intent is just to add a new vehicle
3) If you are doing this change, you may not even need to retrieve the user object from database, if you use EntityManager.getReference()
You normally want to manage bidirectional associations from the (JPA defined) dependent side of the relationship, and within the dependent class. Pseudocode:
class User {
private Set<Vehicle> vehicles;
public void addVehicle(Vehicle vehicle) {
if(vehicle == null) return;
vehicle.setOwner(this);
vehicles.add(vehicle);
}
public void removeVehicle(Vehicle vehicle) {
if(vehicle == null) return;
if(vehicles.remove(vehicle)) {
vehicle.setOwner(null);
}
}
}
Managing the relationship outside of the entities leads to bugs and duplicated code.