CrudRepository - Hibernate - Overwrite existing model with manual set id - java

I have a problem which I try to figure out since many hours now.
I must save a model with manual set id in the database using CrudRepository and Hibernate.
But the manual set of the id is ignored always.
Is it somehow possible, to force
CrudRepository.save(Model m)
to persist the given Model with UPDATE?
The queries always results in INSERT statements, without using the id.
The reason I must do this manually is, that the identifier is not the database ID - it is a ID generated outside as UUID which is unique over multiple databases with this model-entry. This model is shared as serialized objects via hazelcast-cluster.
Following an example:
The database already contains a Model-Entry with the id 1:
id identifier_field_with_unique_constraint a_changing_number
1 THIS_IS_THE_UNIQUE_STRING 10
Now I need to update it. I create a new Model version
Model m = new Model();
m.setIdentifierFieldWithUniqueConstraint(THIS_IS_THE_UNIQUE_STRING);
m.setAChangingNumberField(20);
saveMe(m);
void saveMe(Model m) {
Optional<Model> presentModalOpt = modelCrudRepo.findByIdentField(THIS_IS_THE_UNIQUE_STRING)
if(presentModalOpt.isPresent()) {
// The unique value in my identifier field exists in the database already
// so use that id for the new model, so it will be overwritten
m.setId(modalOpt.get().getId());
} else {
m.setId(null);
}
// This call will now do an INSERT, instead of UPDATE,
// even though the id is set in the model AND the id exists in the database!
modelCrudRepo.save(m);
// ConstraintViolationException for the unique identifier field.
// It would be a duplicate now, which is not allowed, because it uses INSERT instead of UPDATE
}
The id Field is tagged with #Id and #GeneratedValue annotation (for the case that the id is null and the id should be generated)
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
I even tried to changed this field only to an #Id field without #GeneratedValue and generate the ID always on my own. It had no effect, it always used INSERT statements, never UPDATE.
What am I doing wrong?
Is there another identifier for the CrudRepository that declares the model as an existing one, other than the id?
I'm happy for any help.

CrudRepository has only save method but it acts for both insert as well as update.
When you do save on entity with empty id it will do a save.
When you do save on entity with existing id it will do an update
that means that after you used findById for example and changed
something in your object, you can call save on this object and it
will actually do an update because after findById you get an object
with populated id that exist in your DB.
In your case you are fetching the records based on a field (unique) But records will update only when the model object has a existing primary key value
In your code there should be presentModalOpt instead of modalOpt
void saveMe(Model m) {
Optional<Model> presentModalOpt = modelCrudRepo.findByIdentField(THIS_IS_THE_UNIQUE_STRING)
if(presentModalOpt.isPresent()) { // should be presentModalOpt instead of modalOpt
} else {
m.setId(null);
}
modelCrudRepo.save(m);
}
See the default implementation -
/*
* (non-Javadoc)
* #see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
*/
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}

Related

Java/SpringBoot Web app. Insert new row with auto-incremented id column

I am trying to store a new row using a few input lines on a web app into an SQL table. My jsp has all the input rows I need. However, I need to store the new object without inputting a new Id because it's auto incremented. I'm able to call my constructor to store everything else but the id.
my code for that section so far is:
#RequestMapping(value = "/save", method = RequestMethod.POST)
public ModelAndView save
//Index connect
(#RequestParam("id") String id, #RequestParam("type") String animalType,
#RequestParam("name") String animalName, #RequestParam("age") int animalAge){
ModelAndView mv = new ModelAndView("redirect:/");
AnimalConstruct newAnimal;
newAnimal.setAnimalType(animalType);
newAnimal.setAnimalName(animalName);
newAnimal.setAnimalAge(animalAge);
animals.save(newAnimal);
mv.addObject("animalList", animals.findAll());
return mv;
So if I wanted to store "(id)11, (type)bird, (name)patty, (age)5" and I'm only making the type, name, and age inputtable, what should I do for the id? The object technically injects the id as empty I think, but then I get thrown an error. I'm very new to java and Springboot and have very weak skills in both.
The magic happens with a JPA implementation (Hibernate, for instance). Just annotate your id field like:
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
When saving the object, the id will be auto-generated and stored.
Check some similar questions: Hibernate Auto Increment ID and How to auto generate primary key ID properly with Hibernate inserting records
You should not pass the ID when you expect to create an object.
#RequestMapping(value = "/protected", method = RequestMethod.POST)
public RouteDocument doPost(#RequestBody RouteDocument route) throws ControllerException {
createNewRoute(route);
return route;
}
In the previous example, the method createNewRoute, calls the database, in my case using spring JpaTemplate to save it. The object route has an ID property that is filled by JpaTemplate.save. Consequently the doPost return object returns you the same object you passed as parameter BUT with the automatically assigned ID.
Annotate your id column in the bean with :
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private long id;
As answered by #pedrohreis above you can also use GenerationType.AUTO but only if your sole purpose is to make autoincrement id then I prefer GenerationType.IDENTITY
Also, looking forward in your project if you wanna disables batch updates on your data then you should use GenerationType.IDENTITY.
Refer : hibernate-identifiers

Update entity if already exists or create using spring jpa

I am new to spring data jpa. I have a scenario where I have to create an entity if not exists or update based on non primary key name.Below is the code i wrote to create new entity,it is working fine,but if an already exists record ,its creating duplicate.How to write a method to update if exists ,i usually get list of records from client.
#Override
#Transactional
public String createNewEntity(List<Transaction> transaction) {
List<Transaction> transaction= transactionRespository.saveAll(transaction);
}
Add in your Transaction Entity on variable called name this for naming as unique:
#Entity
public class Transaction {
...
#Column(name="name", unique=true)
private String name;
...
}
Then you won't be able to add duplicate values for name column.
First, this is from google composite key means
A composite key is a combination of two or more columns in a table that can be used to uniquely identify each row in the table when the columns are combined uniqueness is guaranteed, but when it taken individually it does not guarantee uniqueness.
A composite key with an unique key is a waste.
if you want to update an entity by jpa, you need to have an key to classify if the entity exist already.
#Transactional
public <S extends T> S save(S entity) {
if(this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
There are two ways to handle your problem.
If you can not get id from client on updating, it means that id has lost its original function. Then remove your the annotation #Id on your id field,set name with #Id. And do not set auto generate for it.
I think what you want is an #Column(unique = true,nullable = false) on your name field.
And that is the order to update something.
Transaction t = transactionRepository.findByName(name);
t.set.... //your update
transactionRepository.save(t);

Updating Existing Entity in JPA

I'm using JPA with Hibernate 5.2.10.Final (Oracle database), and deploying on Weblogic 12.2.1.
Let's say I have 2 tables: Customer and LastActivity:
Customer {
id int,
name String,
last_activity_id int not null
}
LastActivity {
id int,
customerName String,
date Date
}
There is a One to Many relationship: a Customer has a single Activity and one Activity has many Customers.
I have a functionality of adding a Customer, when it happens the record in LastActivity table must be created if it doesn't exist for that Customer, otherwise the date must be updated.
My code looks like this (simplified for the purpose of the question):
public Response createCustomer(Request request) {
String name = request.getName();
Customer customer = new Customer(name);
LastActivity activity = activityDao.findByCustomerName(name)
.orElseGet(LastActivity.from(name));
activity.setDate(ZonedDateTime.now());
customer.setActivity(activityDao.update(activity));
return Response.of(customer);
}
My update method is straightforward:
return entityManager.merge(entity);
When I add a new Customer and an Activity that doesn't exist yet ― it is created correctly with the date I specified. The problem is when the activity already exists ― the update doesn't happen. In the logs there is just a select query on Activities table, then correct insert on Customers table, but the date is old.
Some things I tried:
public T update(T entity) {
EntityManager manager = getEntityManager();
T updated = manager.contains(entity) ? entity : manager.persist(entity);
manager.flush();
return updated;
}
Same thing, nothing changed. Also:
Without flushing
Doing merge instead of just returning entity when contains returns true
Just a flush by itself
Nothing since the entity is "attached"
Tried adding CascadeType.MERGE...still nothing. Only thing that worked was this:
public T update(T entity) {
EntityManager manager = getEntityManager();
manager.detach(entity);
return manager.merge(entity);
}
It did what I wanted it to do, but it added extra select query on Activity table (simply by ID, but still, I would like to avoid that).
I actually managed to "solve" the problem by using CriteriaUpdate, but I don't like this and it seems like I lack of some fundamental knowledge about JPA/Hibernate so I don't just want to leave it like this.

JPA ID Generation Strategy

I defined a generator for a JPA class:
<sequence-generator name="MY_SEQ" allocation-size="-1"
sequence-name="MY_SEQ"
initial-value="100000000" />
There are cases where I already have an ID for an entity but when I insert the Entity the Id gets generated using the generator.
Is it possible to define a generator that will only generate an Id when one does not exist?
I am using Hibernate as a JPA Provider.
Thank you
I couldn't find a way to do this in JPA so I used Hibernate EJB3 event listeners. I over rode the saveWithGeneratedId to use reflection to check the entity for an #Id annotation and then to check that field for a value. If it has a value then I call saveWithRequestedId instead. Other wise I let it generate the Id. This worked well because I can still use the sequence for Hibernate that is set up if I need an Id. The reflection might add overhead so I might change it a little. I was thinking of having a getId() or getPK() method in all entities so I don't have to search for which field is the #Id.
Before I used reflection I tried calling session.getIdentifier(entity) to check but I was getting TransientObjectException( "The instance was not associated with this session" ). I couldn;t figure out how to get the Entity into the session without saving it first so I gave up. Below is the listener code I wrote.
public class MergeListener extends org.hibernate.ejb.event.EJB3MergeEventListener
{
#Override
protected Serializable saveWithGeneratedId(Object entity, String entityName, Object anything, EventSource source, boolean requiresImmediateIdAccess) {
Integer id = null;
Field[] declaredFields = entity.getClass().getDeclaredFields();
for (Field field : declaredFields) {
Id annotation = field.getAnnotation(javax.persistence.Id.class);
if(annotation!=null) {
try {
Method method = entity.getClass().getMethod("get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1));
Object invoke = method.invoke(entity);
id = (Integer)invoke;
} catch (Exception ex) {
//something failed (method not found..etc) , keep going anyway
}
break;
}
}
if(id == null ||
id == 0) {
return super.saveWithGeneratedId(entity, entityName, anything, source, requiresImmediateIdAccess);
} else {
return super.saveWithRequestedId(entity, id, entityName, anything, source);
}
}
}
I then had to add the listener to my persistence.xml
<property name="hibernate.ejb.event.merge" value="my.package.MergeListener"/>
it's not a good Idea, sequences are used for surrogate keys, are meaningless in the business sense but assures you, there won't be duplicates thus no error at inserting time.

hibernate auto_increment getter/setter

I have a class which is mapped to a table using the hibernate notations of auto increment. This class works fine when I set values and update this to the database and I get a correct updated value in the table.
But the issue is when I create a new object of this class and try to get the id, it returns me a 0 instead of the auto_incremented id.
The code of the class is
#Entity(name="babies")
public class Baby implements DBHelper{
private int babyID;
#Id
#Column(name="babyID", unique=true, nullable= false)
#GeneratedValue(strategy = GenerationType.AUTO)
public int getBabyID() {
return babyID;
}
public void setBabyID(int babyID) {
this.babyID = babyID;
}
}
The code I use to get the persistent value is
Baby baby = new Baby();
System.out.println("BABY ID = "+baby.getBabyID());
This returns me a
BABY ID = 0
Any pointers would be appreciated.
Thanks,
Sana.
Hibernate only generates the id after an entity becomes persistent, ie after you have saved it to the database. Before this the object is in the transient state. Here is an article about the Hibernate object states and lifecycle
The ID is set by hibernate when object is saved and became persistable.
The annotation are only informing hibernate, how he should behave with class, property, method that annotation refer to.
Another thing if You have current id value how hibernate, would be able to recognize that he should insert or only update that value.
So this is normal expected behavior.

Categories