Following the in-database example in https://docs.jboss.org/hibernate/orm/5.1/topical/html_single/generated/GeneratedValues.html#__valuegenerationtype_meta_annotation, I am able to get hibernate to produce insert statements that look like
insert into my_table (col_1, col_2,..., col_n, last_updated) values (?, ?,... current_timestamp)
However, I also want it to produce update statements that look like
update my_table set col_1=?, col_2=?,..., col_n=?, last_updated = current_timestamp where ...
but instead I seem to just be getting
update my_table set col_1=?, col_2=?,..., col_n=? where ...
Here's a snippet of Java code I'm using:
public class MyEntity {
...
#LastUpdatedColumn
#Column(name = "last_updated")
Instant last_updated;
#ValueGenerationType(generatedBy = LastUpdatedGeneration.class)
#Retention(RetentionPolicy.RUNTIME)
public #interface LastUpdatedColumn{
}
public static class LastUpdatedGeneration implements
AnnotationValueGeneration<LastUpdatedColumn> {
#Override
public void initialize(LastUpdatedColumn annotation, Class<?> propertyType) {
}
#Override
public GenerationTiming getGenerationTiming() {
return GenerationTiming.ALWAYS;
}
#Override
public ValueGenerator<?> getValueGenerator() {
return null;
}
#Override
public boolean referenceColumnInSql() {
return true;
}
#Override
public String getDatabaseGeneratedReferencedColumnValue() {
return "current_timestamp";
}
}
...
and elsewhere I use Spring Boot/Spring JPA to just do:
myRepository.saveAll(setOfMyEntities)
My main requirement here is to use the insert time from the database (rather than from the application server), but I'd also like to avoid having to use database triggers (though Postgres triggers would be acceptable).
In Hibernate 5.2 and above you can do this:
#Version
#Column(name = "last_updated")
Instant lastUpdated;
This has the additional benefit of rolling back a transaction if the row has been updated by another thread.
IDEs and validators might tell you that this is an invalid type to use the #Version annotation on, because this is a Hibernate-specific support, and not part of JPA 2.2 or below.
This should work as you can see in the following test: https://github.com/hibernate/hibernate-orm/blob/6c5e1726093a5347551b61d9e84ae9dd9f08ece6/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java
Try reproducing the issue with plain Hibernate and the latest Hibernate version. Maybe it's a bug or a problem with Spring Data JPA that you are facing.
Related
I'm currently playing around on Spring boot 1.4.2 in which I've pulled in Spring-boot-starter-web and Spring-boot-starter-jpa.
My main issue is that when I save a new entity it works fine (all cool).
However if I save a new product entity with the same id (eg a duplicate entry), it does not throw an exception. I was expecting ConstrintViolationException or something similar.
Given the following set up:
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
ProductRepository.java
#Repository
public interface ProductRepository extends JpaRepository<Product, String> {}
JpaConfig.java
#Configuration
#EnableJpaRepositories(basePackages = "com.verric.jpa.repository" )
#EntityScan(basePackageClasses ="com.verric.jpa")
#EnableTransactionManagement
public class JpaConfig {
#Bean
JpaTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}
Note JpaConfig.java and Application.java are in the same package.
ProductController.java
#RestController
#RequestMapping(path = "/product")
public class ProductController {
#Autowired
ProductRepository productRepository;
#PostMapping("createProduct")
public void handle(#RequestBody #Valid CreateProductRequest request) {
Product product = new Product(request.getId(), request.getName(), request.getPrice(), request.isTaxable());
try {
productRepository.save(product);
} catch (DataAccessException ex) {
System.out.println(ex.getCause().getMessage());
}
}
}
and finally Product.java
#Entity(name = "product")
#Getter
#Setter
#AllArgsConstructor
#EqualsAndHashCode(of = "id")
public class Product {
protected Product() { /* jpa constructor*/ }
#Id
private String id;
#Column
private String name;
#Column
private Long price;
#Column
private Boolean taxable;
}
The getter, setter and equalsHashcode.. are lombok annotations.
Miscellaneous:
Spring boot : 1.4.2
Hibernate ORM: 5.2.2.FINAL
This issue happens regardless if I annotate the controller with or without #Transactional
The underlying db shows the exception clearly
2016-11-15 18:03:49 AEDT [40794-1] verric#stuff ERROR: duplicate key value violates unique constraint "product_pkey"
2016-11-15 18:03:49 AEDT [40794-2] verric#stuff DETAIL: Key (id)=(test001) already exists
I know that is better (more common) to break the data access stuff into its own service layer instead of dumping it in the controller
The semantics of the controller aren't ReST
Things I've tried:
Spring CrudRepository exceptions
I've tried implementing the answer from this question, unfortunately my code never ever hits the DataAccesException exception
Does Spring JPA throw an error if save function is unsuccessful?
Again similar response to the question above.
http://www.baeldung.com/spring-dataIntegrityviolationexception
I tried adding the bean to my JPAconfig.java class that is:
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
But nothing seemed to happen.
Sorry for long post, ty in advance
My solution is a lot cleaner. Spring Data already provides a nice way for us to define how an entity is considered to be new. This can easily be done by implementing Persistable on our entities, as documented in the reference.
In my case, as is the OP's, the IDs come from an external source and cannot be auto generated. So the default logic used by Spring Data to consider an entity as new if the ID is null wouldn't have worked.
#Entity
public class MyEntity implements Persistable<UUID> {
#Id
private UUID id;
#Transient
private boolean update;
#Override
public UUID getId() {
return this.id;
}
public void setId(UUID id) {
this.id = id;
}
public boolean isUpdate() {
return this.update;
}
public void setUpdate(boolean update) {
this.update = update;
}
#Override
public boolean isNew() {
return !this.update;
}
#PrePersist
#PostLoad
void markUpdated() {
this.update = true;
}
}
Here, I have provided a mechanism for the entity to express whether it considers itself new or not by means of another transient boolean property called update. As the default value of update will be false, all entities of this type are considered new and will result in a DataIntegrityViolationException being thrown when you attempt to call repository.save(entity) with the same ID.
If you do wish to perform a merge, you can always set the update property to true before attempting a save. Of course, if your use case never requires you to update entities, you can always return true from the isNew method and get rid of the update field.
The advantages of this approach over checking whether an entity with the same ID already exists in the database before saving are many:
Avoids an extra round trip to the database
We cannot guarantee that by the time one thread has determined that this entity doesn't exist and is about to persist, another thread doesn't attempt to do the same and result in inconsistent data.
Better performance as a result of 1 and having to avoid expensive locking mechanisms.
Atomic
Simple
EDIT: Don't forget to implement a method using JPA callbacks that sets the correct state of the update boolean field just before persisting and just after loading from the database. If you forget to do this, calling deleteAll on the JPA repository will have no effect as I painfully found out. This is because the Spring Data implementation of deleteAll now checks if the entity is new before performing the delete. If your isNew method returns true, the entity will never be considered for deletion.
I think you are aware of CrudRepository.save() is used for both insert and update. If an Id is non existing then it will considered an insert if Id is existing it will be considered update. You may get an Exception if your send the Id as null.
Since you don't have any other annotations apart from #Id on your id variable, The Unique Id generation must be handled by your code Or else you need to make use of #GeneratedValue annotation.
To build upon Shazins answer and to clarify. the CrudRepositroy.save() or JpaRespository.saveAndFlush() both delegate to the following method
SimpleJpaRepository.java
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
Hence if a user tries to create a new entity that so happens to have the same id as an existing entity Spring data will just update that entity.
To achieve what I originally wanted the only thing I could find was to drop back down to JPA solely, that is
#Transactional
#PostMapping("/createProduct")
public Product createProduct(#RequestBody #Valid Product product) {
try {
entityManager.persist(product);
entityManager.flush();
}catch (RuntimeException ex) {
System.err.println(ex.getCause().getMessage());
}
return product;
}
Here if we try to persist and new entity with an id already existing in the database it will throw will throw the constraint violation exception as we originally wanted.
Note that there are 3 scenarios here:
1. Setting ID manually
If there is no choice(like the OP), i.e if you are setting your own id "manually", Spring Data JPA is assuming that you want to check if there are duplicates(hence the SELECT), so it will do a "(i)SELECT + (ii)INSERT" if there is no existing record or a "(i)SELECT + (ii)UPDATE" if there is already an existing record.
In short, 2 SQLs!
2. Use an ID Generator
Cleaner & better, for example:
#Id
#GeneratedValue(generator = "my-uuid")
#GenericGenerator(name = "my-uuid", strategy = "uuid2")
private UUID id;
Result: there is ALWAYS only 1 INSERT statement.
3. Implement Persistable and isNew()
This has already been brilliantly answered by #adarshr, but is also more painful, i.e to implement Persistable(instead of Serializable), and implement the isNew() method.
Result: Also, 1 INSERT statement.
According to Spring Data documentation Spring persists an entity if does not exists or merge, this means update, the existing one:
Saving an entity can be performed via the CrudRepository.save(…)-Method. It will persist or merge the given entity using the underlying JPA EntityManager. If the entity has not been persisted yet Spring Data JPA will save the entity via a call to the entityManager.persist(…)-Method, otherwise the entityManager.merge(…)-Method will be called.
The system I'm working on has a bunch of legacy data where boolean values have been stored as 'Y' and 'N'. New tables use a BIT column instead and simply store 0 and 1. No table mixes the two approaches.
To support the legacy tables we have the following converter:
#Converter(autoApply = false)
public class BooleanToStringConverter implements AttributeConverter<Boolean, String> {
private Logger.ALogger Log = Logger.of(BooleanToStringConverter.class);
#Override
public String convertToDatabaseColumn(final Boolean attribute) {
Log.debug("Converting the boolean value {}", attribute);
if (attribute == null) {
return "N";
}
return attribute ? "Y" : "N";
}
#Override
public Boolean convertToEntityAttribute(final String dbData) {
return "Y".equalsIgnoreCase(dbData);
}
}
As this only needs to apply to certain entities the autoApply property has been set to false.
I'm now creating a brand new entity, with a new table. It has two boolean properties, both using the BIT column style instead of Y/N:
#Entity
#Table(name = "MyEntity")
public class MyEntity {
#Id
#Column(name = "MyEntityId")
private Long id;
#Column(name = "IsClosed")
private Boolean closed;
...
}
Note that I have not applied the #Convert annotation.
I have a query that needs to filter out any rows where the entity is closed:
query.where().eq(CLOSED, Boolean.FALSE)
It is at this point that my problem arises. Whenever this query is run I see the log message from the BooleanToStringConverter being written to the logs and indeed, if I dump the actual SQL that was executed from the MySQL database then I can see that the converter did actually get applied to the boolean property, creating the following SQL fragment:
select <columns>
from MyEntity t0
where <other predicates>
and t0.IsClosed = 'N'
order by <order clause>
This is obviously wrong - the converter shouldn't have been applied, it's not set to be automatic and the closed property isn't annotated with #Convert.
I tried to work around this by creating a second converter:
#Converter(autoApply = true)
public class BooleanConverter implements AttributeConverter<Boolean, Boolean> {
private Logger.ALogger Log = Logger.of(BooleanConverter.class);
#Override
public Boolean convertToDatabaseColumn(final Boolean attribute) {
Log.debug("Processing the value {}.", attribute);
return attribute;
}
#Override
public Boolean convertToEntityAttribute(final Boolean dbData) {
return dbData;
}
}
This resulted in both converters being applied to the property and I see both debug statements appearing in the logs.
2019-07-29 14:19:53,994 [dispatcher-69] DEBUG BooleanConverter Processing the value false.
2019-07-29 14:19:53,994 [dispatcher-69] DEBUG BooleanToStringConve I'm Converting the boolean value false
Next I tried explicitly setting the converter to use on the entity itself (I hoped this might change the order that the converters were getting applied in so that it'd end up as true/false despite the other converter running):
#Entity
#Table(name = "MyEntity")
public class MyEntity {
#Id
#Column(name = "MyEntityId")
private Long id;
#Convert(converter = BooleanConverter.class)
#Column(name = "IsClosed")
private Boolean closed;
...
}
With exactly the same result; both converters are applied to the value sequentially, with the BooleanToStringConverter having the last laugh and mangling the predicate.
I would rather keep the BooleanToStringConverter as it makes dealing with the legacy data a bit less painful, but unless I can figure out why it's being applied when it shouldn't it's looking likely that I'll have to delete it.
I'm using Ebean version 4.1.3 and Play! 2.6.21
How can I stop this rogue converter from applying itself to properties that it has no right to be touching?
This is a (now) known limitation of Ebean, as described by Issue 1777 on Ebean's GitHub page. It is not planned to be fixed at the time of writing.
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 am Using SqlServer 2012 and my Entity is
public class Something {
private Date rq;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "rq")
#Formula("CONVERT(DATE,rq)")
public Date getRq() {
return Rq;
}
public void setRq(Date rq) {
this.Rq = rq;
}
}
Hibernate debug log :
Hibernate:
select
CONVERT(dnypowergr0_.DATE,
dnypowergr0_.rq) as formula0_
from
db.dbo.something dnypowergr0_
I want to get the result of 'rq' that can truly 'convert' but as the log shows, the first argument of 'convert' was added an alias of the table, So this sql is error.
Have I written wrong code or used part of '#Formula' ?
Not sure how to make hibernate to do not insert table alias where it is not needed. But there is a workaround.
You can define a transient attribute (something like convertedRq) and convert value in Java. In this case rq will contain pure value of rq field, convertedRq will be calculated on fly.
Update: solution was posted here Hibernate #formula is not supportinng Cast() as int for teradata database :
public class Oracle10gDialectExtended extends Oracle10gDialect {
public Oracle10gDialectExtended() {
super();
/* types for cast: */
registerKeyword("int");
// add more reserved words as you need
}
}
(c) Sergio M C Figueiredo
I'm using spring-data's repositories - very convenient thing but I faced an issue. I easily can update whole entity but I believe it's pointless when I need to update only a single field:
#Entity
#Table(schema = "processors", name = "ear_attachment")
public class EARAttachment {
private Long id;
private String originalName;
private String uniqueName;//yyyy-mm-dd-GUID-originalName
private long size;
private EARAttachmentStatus status;
to update I just call method save. In log I see the followwing:
batching 1 statements: 1: update processors.ear_attachment set message_id=100,
original_name='40022530424.dat',
size=506,
status=2,
unique_name='2014-12-16-8cf74a74-e7f3-40d8-a1fb-393c2a806847-40022530424.dat'
where id=1
I would like to see some thing like this:
batching 1 statements: 1: update processors.ear_attachment set status=2 where id=1
Spring's repositories have a lot of facilities to select something using name conventions, maybe there is something similar for update like updateForStatus(int status);
You can try something like this on your repository interface:
#Modifying
#Query("update EARAttachment ear set ear.status = ?1 where ear.id = ?2")
int setStatusForEARAttachment(Integer status, Long id);
You can also use named params, like this:
#Modifying
#Query("update EARAttachment ear set ear.status = :status where ear.id = :id")
int setStatusForEARAttachment(#Param("status") Integer status, #Param("id") Long id);
The int return value is the number of rows that where updated. You may also use void return.
See more in reference documentation.
Hibernate offers the #DynamicUpdate annotation. All we need to do is to add this annotation at the entity level:
#Entity(name = "EARAttachment ")
#Table(name = "EARAttachment ")
#DynamicUpdate
public class EARAttachment {
//Code omitted for brevity
}
Now, when you use EARAttachment.setStatus(value) and executing "CrudRepository" save(S entity), it will update only the particular field. e.g. the following UPDATE statement is executed:
UPDATE EARAttachment
SET status = 12,
WHERE id = 1
You can update use databind to map #PathVariable T entity and #RequestBody Map body. And them update body -> entity.
public static void applyChanges(Object entity, Map<String, Object> map, String[] ignoreFields) {
map.forEach((key, value) -> {
if(!Arrays.asList(ignoreFields).contains(key)) {
try {
Method getMethod = entity.getClass().getMethod(getMethodNameByPrefix("get", key));
Method setMethod = entity.getClass().getMethod(getMethodNameByPrefix("set", key), getMethod.getReturnType());
setMethod.invoke(entity, value);
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}
});
}