I have been trying to implement entity updating using #DynamicUpdate. Documentation (http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html_single/) says:
dynamicInsert / dynamicUpdate (defaults to false): specifies that INSERT / UPDATE SQL should be generated at runtime and contain only the columns whose values are not null.
But I didn't manage to make it works, so I dug into the sources of AbstractEntityPersister, and saw this:
generateUpdateString( propsToUpdate, j, oldFields, j == 0 && rowId != null )
It is a method for generating SQL query string, based on 'propsToUpdate' which is boolean array obtained from:
getPropertiesToUpdate( dirtyFields, hasDirtyCollection );
where 'dirtyFields' is an integer array of id's of 'dirty' columns.
But nowhere in
getPropertiesToUpdate(final int[] dirtyProperties, final boolean hasDirtyCollection)
i was able to find mechanism for checking if modified column is not null, so even when entity i want to merge has some null fields, all of them are updated in the DB, overriding existing data with nulls.
My question is: where is an error in my reasoning?
EDIT:
Here's my code, as Chaitanya requested:
Entity:
#SuppressWarnings("serial")
#Entity(name = "t_quiz_groups")
#DynamicUpdate
#SelectBeforeUpdate
public class QuizGroupEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "group_id")
private long id;
#Column(name = "description", nullable = false)
private String description;
#Column(name = "create_date", nullable = false)
#Column(name = "create_date")
private Calendar createDate;
+setters and getters
Then, I have a service (#Transactional) method:
public void update(long groupId, String newDescription) throws ServiceException {
QuizGroupEntity quizGroupEntity = quizRepository.getGroupById(groupId);
ExpectedNotNull.of(quizGroupEntity, RegistryErrorCodes.QUIZ_GROUP_NOT_EXISTS);
quizGroupEntity.setCreateDate(null); //for testing purposes i am setting another
field as null (shouldnt be updated then, right?)
quizRepository.lock(quizGroupEntity, LockModeType.WRITE);
quizGroupEntity.updateDescription(newDescription); // new description
quizRepository.merge(quizGroupEntity);
}
And now, in AbstractEntityPersiste, method:
getPropertiesToUpdate( dirtyFields, hasDirtyCollection );
is called, and dirtyFields[] consists of two 'dirty' columns: [0,2] - and that is correct, those two columns were modified (date and description, date set to null)
Then, generateUpdateString( propsToUpdate, j, oldFields, j == 0 && rowId != null ) is called, with 'propsToUpdate' looking like this:
[true, false, true, false, true]
Which is wrong, because indeed first and third columns were modified, but first one is set to null, that is why
generateUpdateString( propsToUpdate, j, oldFields, j == 0 && rowId != null )
Generates query:
[update t_quiz_groups set create_date=?, description=?, version=? where group_id=? and version=?]
That will modify create_date, and override it with null value.
Related
I have a problem updating my entity. As you can see I have three entities.
LabelValueEntity holds a list from class LabelSwitchEntity.
LabelSwitchEntity holds a list from class SwitchCaseEntity.
As you can see from my SQL statement the fields name and labelValueUUID are unique. Only one row of that combination is allowed in my table.
When I update the parent entity (LabelValueEntity) with a new list of class LabelSwitchEntity I want Hibernate to delete the old list itself and create new entities. It's kinda hard to update the child one by one. That's why I want to delete all the related children directly.
When I update a LabelValueEntity and provide it with a list that contains a LabelSwitchEntity with a unique name and labelValueUUID (Already existing entity in the DB) combination I get a unique constraint violation exception. Well, that error is clear because the combination exists in the DB as I said. I would expect here that Hibernate is smart enough to delete the child before inserting it.
What am I doing wrong?
#Entity
#Table(name = "label_value")
class LabelValueEntity(uuid: UUID? = null,
...
#OneToMany(
mappedBy = "labelValueUUID",
cascade = [CascadeType.ALL],
fetch = FetchType.EAGER,
orphanRemoval = true)
#Fetch(FetchMode.SUBSELECT)
val labelSwitchEntities: List<LabelSwitchEntity>? = emptyList()
) : BaseEntity(uuid)
#Entity
#Table(name = "label_switch")
class LabelSwitchEntity(uuid: UUID? = null,
#Column(name = "label_value_uuid", nullable = false)
val labelValueUUID: UUID,
#OneToMany(
mappedBy = "labelSwitchUUID",
cascade = [CascadeType.ALL],
fetch = FetchType.EAGER,
orphanRemoval = true
)
val switchCaseEntities: List<SwitchCaseEntity>,
#Column
val name: String,
...
) : BaseEntity(uuid)
#Entity
#Table(name = "switch_case")
class SwitchCaseEntity(uuid: UUID? = null,
...
#Column(name = "label_switch_uuid", nullable = false)
val labelSwitchUUID: UUID
) : BaseEntity(uuid)
CREATE TABLE label_switch
(
uuid UUID NOT NULL PRIMARY KEY,
label_value_uuid UUID REFERENCES label_value(uuid) ON DELETE CASCADE,
name CHARACTER VARYING (255) NOT NULL,
UNIQUE (label_value_uuid, name)
);
CREATE TABLE switch_case
(
uuid UUID NOT NULL PRIMARY KEY,
label_switch_uuid UUID NOT NULL REFERENCES label_switch(uuid) ON DELETE CASCADE
);
BaseEntity
#MappedSuperclass
abstract class BaseEntity(givenId: UUID? = null) : Persistable<UUID> {
#Id
#Column(name = "uuid", length = 16, unique = true, nullable = false)
private val uuid: UUID = givenId ?: UUID.randomUUID()
#Transient
private var persisted: Boolean = givenId != null
override fun getId(): UUID = uuid
#JsonIgnore
override fun isNew(): Boolean = !persisted
override fun hashCode(): Int = uuid.hashCode()
override fun equals(other: Any?): Boolean {
return when {
this === other -> true
other == null -> false
other !is BaseEntity -> false
else -> getId() == other.getId()
}
}
#PostPersist
#PostLoad
private fun setPersisted() {
persisted = true
}
}
Here is where the transactions come in. If you handle all that logic inside a method annotated with #Transactional (be it from javax or spring), hibernate will do the dirty checking within its bounds, but not validation.
In other words - as long as there is a transaction, hibernate keeps track of changes made to the managed entities, but it does not validate it - it's the responsibility of the database. When being processed in memory, entities are not checked for consistency. Just make sure you're flushing the consistent state to the database (entities at the commit phase of transaction should be consistent).
Other options are possible, when your database allows for it, for example in postgres you can mark the constraints as deferred. When a constraint is defined as deferred, it's not checked within an opened transaction, only on commit.
After some struggle i came out with this solution:
fun updateLabelValue(updatedLabelValue: LabelValueDTO): LabelValueDTO {
val updatedEntity = updatedLabelValue.copy(labelSwitches = emptyList())
labelValueRepository.saveAndFlush(labelValueMapper.map(updatedEntity))
val labelValueEntity = labelValueMapper.map(updatedLabelValue)
return labelValueMapper.map(labelValueRepository.save(labelValueEntity))
}
It's pretty ugly and not really what i wanted. I'm saving the entity first with an empty array so that Hibernate can delete the orphans, after that i do an insert with the updated labelValue with the new orphans.
If you have suggestions or a better solution, drop it down below thanks.
Previously i was not getting any error but suddenly i am started getting error :
javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [baag.betl.dbimporter.esmatrans.db.EquSecurityReferenceData#baag.db.SECU#59c70ceb]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:116) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:155) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:162) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:787) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:765) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
There is no sequence created for any column in oracle database table. Also there is combination of unique key column for esn and techid. I dont want to create new sequence column in my database table.
I think i cannot use #GeneratedValue and set it to Auto for unique columns otherwise i will get hibernate sequence error.
I am also doing clear and flush after every 1000 records processed.
if (secuData != null) {
sessionFactory.getCurrentSession().persist(secuData);
}
i++;
if (i % 1000 == 0) {
sessionFactory.getCurrentSession().flush();
sessionFactory.getCurrentSession().clear();
}
To have a composite primary key, you can either have the use of #IdClass or #Embeddable key approach.
To continue with the #IdClass approach you need to follow some rules,
The composite primary key class must be public
It must have a no-arg constructor
It must define equals() and hashCode() methods
It must be Serializable
So in your case, the class would look like,
#Entity
#Table( name = "SECU" )
#IdClass( SECU.class )
public class SECU implements Serializable
{
#Id
#Column(name = "columnName") // use the correct column name if it varies from the variable name provided
protected String esn;
#Id
#Column(name = "columnName") // use the correct column name if it varies from the variable name provided
protected BigDecimal techrcrdid;
#Column(name = "columnName") // use the correct column name if it varies from the variable name provided
protected BigDecimal preTradLrgInScaleThrshld;
#Column(name = "columnName") // use the correct column name if it varies from the variable name provided
#Temporal( TemporalType.TIMESTAMP)
protected LocalDateTime CreDt;
#Column(name = "columnName") // use the correct column name if it varies from the variable name provided
protected String fullnm;
#Override
public boolean equals( Object o )
{
if( this == o ) return true;
if( !( o instanceof SECU ) ) return false;
SECU secu = ( SECU ) o;
return Objects.equals( esn, secu.esn ) &&
Objects.equals( techrcrdid, secu.techrcrdid ) &&
Objects.equals( preTradLrgInScaleThrshld, secu.preTradLrgInScaleThrshld ) &&
Objects.equals( CreDt, secu.CreDt ) &&
Objects.equals( fullnm, secu.fullnm );
}
#Override
public int hashCode()
{
return Objects.hash( esn, techrcrdid, preTradLrgInScaleThrshld, CreDt, fullnm );
}
// getters and setters
}
Also double check your getters and setters in each entity class, the once provided in your question seems incorrect.
Your entity class appears to be modelling a composite primary key, but I only see 2 #Id and no #IdClass annotation used which is necessary.
I want one of the fields to be ignored when called save() method. The field is gonna get populated automatically by the database and returned. It should be treated as a read-only field.
I am concerned about private Timestamp ts; field:
#Entity
#Table(name = "time_series", schema = "ms")
#IdClass(Reading.class)
public class Reading implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "name", nullable = false)
private String sensorName;
#Id
#Column(name = "ts", insertable = false, updatable = false)
private Timestamp ts;
#Column(name = "reading")
private Double value;
...
As you see, I use insertable = false, updatable = false are inside the #Column annotation, so I'd expect that ts is ignored when forming the actual SQL behind the curtain.
#Override
#Transactional(readOnly = false)
public Reading save(Reading r) {
return readingRepository.save(r);
}
ReadingRepository is basically extended Spring's CrudRepository which has save(...) method.
When I save Reading object with ts=null I get an error from Postgres:
ERROR: null value in column "ts" violates not-null constraint
because Spring Data did not actually ignore the ts field based what I see from the log:
insert into ms.time_series (ts, name, reading) values (NULL, 'sensor1', 10.0)
Clearly, I want the query to be without ts like this:
insert into ms.time_series (name, reading) values ('sensor1', 10.0)
Why is the field not being ignored?
Now if you ask me whether my database schema is okay I say yes. When I type SQL query in console without the ts everything is fine. I even tried #Generated and #GeneratedValue annotations. Name and ts are both forming a primary key for the table, however, the result is the same if I make only one of them a PK or if I add an extra surrogate ID column. Same result...
Am I overlooking something or is there maybe a bug in the Spring framework?? I am using Spring 5.1.2 and SpringData 2.1.2
Note: If I use #Transient annotation that persists the insert query correctly but then the field is being ignored completely even on read/fetch.
Many thanks for any help with this!
Try using GenericGenerator and GeneratedValue in your code.
Add the needed annotation and give values to all other members in Reading class, except ts.
Here some examples.
As you say
I get an error from Postgres
If you check the docs it states:
Technically, a primary key constraint is simply a combination of a unique constraint and a not-null constraint.
That's also true for multi-column primary keys (see here)
So, if ts is part of your primary key in the database (as the #Id indicates) it's simply not possible to insert null values in that column.
IMO Hibernate/Spring got nothing to do with that as
insert into ms.time_series (ts, name, reading) values (NULL, 'sensor1', 10.0)
should be equivalent to
insert into ms.time_series (name, reading) values ('sensor1', 10.0)
I set "firm_name" default value in Ben using annotation.
But when I insert data it will add NULL in database.
I want to set default value into database so that I just set the values which are require.
Other column values set as default value which is set into Bean.
Following is my Code.But it is not working. I will inset nNULL value into database.
#Entity
#Table(name = "my_leads")
public class My_leads{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id")
int id;
#Column(name = "name", length = 100,columnDefinition = "varchar(255) default 'NA'")
String name;
#Column(name = "enrtyDate", insertable = false, updatable = false, nullable = false,columnDefinition = "datetime default NOW()")
Date enrtyDate;
#Column(name = "mobileNo", nullable = false, length = 100)
String mobileNo;
#Column(name = "firm_name", length = 100, nullable = false,columnDefinition = "varchar(255) default 'No Refrence'")
String firm_name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getEnrtyDate() {
return enrtyDate;
}
public void setEnrtyDate(Date enrtyDate) {
this.enrtyDate = enrtyDate;
}
public String getMobileNo() {
return mobileNo;
}
public void setMobileNo(String mobileNo) {
this.mobileNo = mobileNo;
}
public String getFirm_name() {
return firm_name;
}
public void setFirm_name(String firm_name) {
this.firm_name = firm_name;
}
}
My_Leads lead=new My_Leads();
lead.setUser(1);
lead.setMobileNo("1234567896");
lead.setName("Sajan");
lead.setPriority(1);
lead.setStage(1);
lead.setCampain(1);
adminService.SaveLead(lead)
The code you wrote is not a way to set a default value by Hibernate - it's actually for a database.
When you create a table in the database you can define a column such way that if you try inserting a null value the default value will be inserted instead. That's what you did here varchar(255) default 'No Refrence'.
If your table is already created, Hibernate is gonna ignore that statement. That statement is used only when Hibernate is creating the schema for you, using #Entity classes. If you go into your database and check column definitions you will see that your column has no default value, since it was not created by Hibernate.
You can delete your schema and let Hibernate create it for you, then the default value will work. Or you can edit your schema manually, adding default value to already existing column. For example:
ALTER TABLE my_leads ALTER COLUMN firm_name SET DEFAULT 'No Refrence'
If you let Hibernate generate your schema and still have this error - make sure those are actually null values, not NULL strings or something.
If you don't want to have the default values inserted by database, but by Hibernate, do this in your #Entity class:
String firm_name = "No Refrence";
Whenever an entity is to be inserted, Hibernate is going to generate the following DML:
INSERT
INTO my_leads (id, name, enrtyDate, mobilNo, firm_name)
VALUES (?, ?, ?, ?, ?)
In your java code, you're simply not setting a value for firm_name so that property is null.
Whether or not the default value for the column is used is going to depend upon what database platform you're using and how it interprets NULL in this case.
For example, MySQL will see that Hibernate bound NULL for the firm_name and will therefore apply the default value for you rather than setting the column as NULL.
For example, SQL Server will see that the INSERT statement contains the field firm_name in the columns section and therefore will ignore the default value and use whatever value is supplied in the values section, thus the column will be set to NULL. The only way SQL Server will use the default value is if the column is omitted from the columns section of the insert statement.
The only way to guarantee that the default value is set regardless of your database platform is to make sure that your entity state adheres to that rule too. This means you either need to initialize firm_name with the default value in the field definition, in the constructor of your class or your business logic that constructs your entities.
I have trying to insert a record into the database (MySQL), using Entity Class and Entity Manager. But one of the field is an auto incrementing primary key, so unless I provide an value manually, the insertion is not successful.
public boolean newContributor(String name, String email, String country, Integer contactable, String address) {
Contributors contributor = new Contributors(); //entity class
contributor.setId(??????); //this is Primary Key field and is of int datatype
contributor.setName(name);
contributor.setEmail(email);
contributor.setCountry(country);
contributor.setContactable(contactable);
contributor.setAddress(address);
em.persist(contributor);
return true;
}
How to solve such problem? Is there a way to tell the entity manager to attempt the insert without the ID field and use NULL value instead.
Update: Here is a portion of the entity class defining the id
...
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 50)
....
Is there a way to tell the entity manager to attempt the insert without the ID field and use NULL value instead?
Sure. You need to remove the #NotNull annotation for id field in the #Entity definition, and also remove the row:
contributor.setId(??????);
from method newContributor(). The reason for this is that the #NotNull annotation enforces a validation check in the JPA stack. It doesn't mean that the field is NOT NULL at a database level. See here a discussion about this issue.
The rest of the code looks fine.