I am using Wicket, JPA+Hibernate in a project
I have the persistence class like this
#Entity
#Table(name = "document", uniqueConstraints = { #UniqueConstraint(columnNames = {
"name"}) })
public class Document
implements Serializable
{
private static final long serialVersionUID = 8496087166198616020L;
#Id
#GeneratedValue
private long id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String state;
.....
Geter && setters
Then
I have the dao implementation for crud operations
One method is
#Override
#Transactional
public void createDocument(Document aDocument)
{
entityManager.persist(aDocument);
}
My Problem is, in the wicketPage, if I use a Wicket Link and it's onclick method doing the following
document.setState("someState");
it updates the value with out calling the createDocument(document) specifically and the database is also up-to-date
But if I use AjaxLink and its onclick method
then, I have to manually update the object otherwise the value will not be updated in the database. My modification should be as follows:
#Override
#Transactional
public void createDocument(Document aDocument)
{
if(aDocument.getId()==0{
entityManager.persist(aDocument);
}
else{
entityManager.merge(aDocument);
}
Then to save the changes in the Database, I have to first update the state and call createDocument() method.
document.setState("someState");
dao.createDocument(document);
Can anyone point out why this happens?
Many thanks
Your database session must be in AutoCommit mode. When you use the Link a "classic" RequestCycle is fired which certainly triggers your OpenSessionInView filter. This way, the document object that you update is still managed by the Persistence Context, thus, the underlying JPA implementation auto commits the change to the database.
When you use the AjaxLink, you're playing with a detached entity, JPA does not manage it and hence does not autocommit it to the database. When you call your createDocument method, you merge your entity with your Persistence Context and save it back.
If you do not want the autocommit to happen, you can set the FlushMode of your entityManager to FlushMode.COMMIT
Related
I'm using Spring boot 2.7.0
And have the next entities in simple:
#Getter
#Setter
#Entity
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Version
private Long version;
private String name;
}
#Getter
#Setter
#Entity
public class Event {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
Account account;
private String message;
}
and jpa repositories:
#Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
}
#Repository
public interface EventRepository extends JpaRepository<Event, Long> {
Page<Event> findAllByAccount(Account account, Pageable pageable);
}
In short I call
eventRepository.findAllByAccount(accountRepository.findById(1), PageRequest.of(1,10));
Problem is every call of last code increases the version field of Account by 1. So question is why? I don't call any update or save method.
And additionally the result of this behaviour is calling of method needs
#Transactional(readonly=false)
Otherwise if I write readonly=true that throws cannot execute UPDATE in a read-only transaction
ADDED:
full code of usage:
#Transactional
public Page<Event> events(Long accountId, int page) {
return eventRepository.findByAccount(findById(accountId), PageRequest.of(page, PAGE_SIZE));
}
#GetMapping("/events")
public List<EventResponse> listEvents(#RequestParam(value = "max", defaultValue = "0") int page) {
return eventService.events(1L, page).stream().map(EventResponse::of).toList();
}
It looks like hibernate is deriving lockMode type as either of WRITE or OPTIMISTIC_FORCE_INCREMENT or PESSIMISTIC_FORCE_INCREMENT based on isolation level of your database. As per reference hibernate decides this pessimistic locking by its own based on database you use.
As per doc, if lockmode type is either of what I mentioned above, Version will get automatically incremented even if you haven't changed anything i.e. even if you haven't do any update or save.
Please check database isolation level & based on that you might get an idea about this.
Edit: as you explicitly setting lockmode as write so my answer validates that because of WRITE mode, your version got incremented automatically.
The problem should be related in the code which is using the result of the find.
If you're modifying entities under a transaction they're going to be modified at the end of the method, when Spring in this case is going to close the transaction. In this part when transaction ends, the JPA provider (for example hibernate) aligns the relative entity record into the database with the 'java entity object' by an update.
I'm sorry. After trim all my code to the posted and debug I found my mistake:
In the begin I was retrieving Account in another method by .lock(Long) method instead of .findById(Long)
lock method is below:
#Lock(LockModeType.WRITE)
#Query("from Account where id = :id")
public Optional<Account> lock(Long id);
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.
I have a form to fill a POJO called Father. Inside it, I have a FotoFather field.
When I save a new Father, I save automatically the object FotoFather (with Hibernate ORM pattern).
FotoFather.fotoNaturalUrl must be filled with the value of Father.id and here is the problem!
When i'm saving Father on the db, of course I still haven't Father.id value to fill FotoFather.fotoNaturalUrl. How can I solve this problem?
Thank you
#Entity
#Table(name = "father")
public class Father implements Serializable{
...
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
...
#OneToOne(targetEntity = FotoFather.class, fetch = FetchType.EAGER)
#JoinColumn(name = "fotoFather", referencedColumnName = "id")
#Cascade(CascadeType.ALL)
private FotoFather fotoFather;
}
FotoFather.class
#Entity
#Table(name = "foto_father")
public class FotoFather.class{
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
...
#Column(name = "foto_natural_url")
private String fotoNaturalUrl;
...
}
If you simply need the complete URL for some application-specific purpose, I would likely err on the side of not trying to store the URL with the ID at all and instead rely on a transient method.
public class FotoFather {
#Transient
public String getNaturalUrl() {
if(fotoNaturalUrl != null && fotoNaturalUrl.trim().length > 0) {
return String.format("%s?id=%d", fotoNaturalUrl, id);
}
return "";
}
}
In fact, decomposing your URLs even more into their minimalist variable components and only storing those in separate columns can go along way in technical debt, particularly if the URL changes. This way the base URL could be application-configurable and the variable aspects that control the final URL endpoint are all you store.
But if you must know the ID ahead of time (or as in a recent case of mine, keep identifiers sequential without loosing a single value), you need to approach this where FotoFather identifiers are generated prior to persisting the entity, thus they are not #GeneratedValues.
In order to avoid issues with collisions at insertion, we have a sequence service class that exposes support for fetching the next sequence value by name. The sequence table row is locked at read and updated at commit time. This prevents multiple sessions from concurrency issues with the same sequence, prevents gaps in the range and allows for knowing identifiers ahead of time.
#Transactional
public void save(Father father) {
Assert.isNotNull(father, "Father cannot be null.");
Assert.isNotNull(father.getFotoFather(), "FotoFather cannot be null.");
if(father.getFotoFather().getId() == null) {
// joins existing transaction or errors if one doesn't exist
// when sequenceService is invoked.
Long id = sequenceService.getNextSequence("FOTOFATHER");
// updates the fotofather's id
father.getFotoFather().setId(id);
}
// save.
fatherRepository.save(father);
}
I think you can do be registering an #PostPersist callback on your Father class. As the JPA spec notes:
The PostPersist and PostRemove callback methods are invoked for an
entity after the entity has been made persistent or removed. These
callbacks will also be invoked on all entities to which these
operations are cascaded. The PostPersist and PostRemove methods will
be invoked after the database insert and delete operations
respectively. These database operations may occur directly after the
persist, merge, or remove operations have been invoked or they may
occur directly after a flush operation has occurred (which may be at
the end of the transaction). Generated primary key values are
available in the PostPersist method.
So, the callback should be called immediately after the Father instance is written to the database and before the FotoFather instance is written.
public class Father(){
#PostPersist
public void updateFotoFather(){
fotofather.setNaturalUrl("/xyz/" + id);
}
}
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).
I'm using Hibernate with jpa and I'm getting LazyInizializationException trying to do a very simple thing. I know what does LazyInizializationException means but I can't understand why it comes while i'm doing everything in the most common and simple way. This is the "one" side of the relationship:
#Entity
public class User implements Serializable{
#Id #GeneratedValue
private int idUser;
private String name;
private String surname;
private String username;
#OneToMany(mappedBy="user")
private List<Device> dev;
...getters and setters...
and this is the "Many" side:
#Entity
public class Device implements Serializable {
#Id #GeneratedValue
private int idDevice;
private String brand;
private String model;
#ManyToOne
#JoinColumn(name="user_fk")
private User user;
...getters and setters...
the jUnit test that throws the exception is:
#Test
public void testLazyUserSnd() {
User u = uDao.getUser(2);
List<Device> devList = u.getDev();
Device aDevice = devList.get(0); // <--- Here the exception is thrown
aDevice.getModel();
I made the relationship as explained in the Hibernate Documentation. Any hint? Am I making some big and stupid mistakes?
While #Xavi's answer is perfectly reasonable, you may not always want to load the devices for a user. If you don't, there are 2 ways of fixing this.
Create an additional method uDao.getUserWithDevices(id) and call that when you know you need devices, otherwise call the uDao.getUser(id).
Encapsulate the test method, and therefore any production code that uses the method, in a transaction. In other words keep the session open as long as you need to.
Personally I'd use the transaction method since as it allows more flexibility and allows JPA to lazy load whenever it needs to. See also http://community.jboss.org/wiki/OpenSessionInView for more interesting information around session lifecycle.
The exception is telling you that you're trying to get some of the lazy-loaded association's elements when the session is closed. Probably you should call u.getDev() or Hibernate.initialize(u.getDev()) inside the dao's method, when the hibernate session is still open.
Or, if you're using Criteria, you could also use setFetchMode to force eager fetching.
public User getUser(String id) {
Session session = getSession();
Criteria criteriaQuery = session.createCriteria(User.class);
criteriaQuery.add(Expression.eq("id", id));
criteriaQuery.setFetchMode("dev", FetchMode.JOIN);
return criteriaQuery.uniqueResult();
}
The entity is probably detached from the session (transaction context) when you access the relation. Try to enclose your test method in a transaction.