I am using:
Web App (a filter opens session. DAO uses getCurrentSession())
Hibernate
Spring (AOP configuration over Service)
xml configuration for all
DTO between Mbean and Service
Well, I have two methods (business service):
service.findUser(..DTO..)
service.updateUser(..DTO..)
update throws org.hibernate.NonUniqueObjectException exception.
How can I avoid that?
I need to use update, not merge.
Thanks in advance.
//MBean.java method
public void testUpdateUser(ActionEvent e) {
System.out.println(name);
ServiceResponse<UserDto> serviceResponse = super.getPrincipalService().findUser(name);
UserDto userDto = serviceResponse.getResponseList().get(0);
//update some properties here
serviceResponse = super.getPrincipalService().updateUser(userDto);
LOG.info("" + serviceResponse);
}
//Service.java: update method
public ServiceResponse<UserDto> updateUser(UserDto userDto) {
LOG.info("");
ServiceResponse<UserDto> serviceResponse = new ServiceResponse<UserDto>();
try {
User user = this.getGlobalMapper().map(userDto, User.class);
//
this.getUserDao().update(user);
userDto = this.getGlobalMapper().map(user, UserDto.class);
serviceResponse.getResponseList().add(userDto);
serviceResponse.setOperationCodeResponse(ServiceResponseCode.OK);
serviceResponse.getMessages().add("Operacion OK");
} catch (Exception e) {
serviceResponse.getMessages().add(e.getMessage());
serviceResponse.setOperationCodeResponse(ServiceResponseCode.MODEL_ERROR);
LOG.error("", e);
}
return serviceResponse;
}
//Exception result
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.softlogia.copi.model.domain.User#155]
at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:696)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:296)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:241)
at org.hibernate.event.internal.DefaultUpdateEventListener.performSaveOrUpdate(DefaultUpdateEventListener.java:55)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:90)
at org.hibernate.internal.SessionImpl.fireUpdate(SessionImpl.java:705)
at org.hibernate.internal.SessionImpl.update(SessionImpl.java:697)
at org.hibernate.internal.SessionImpl.update(SessionImpl.java:693)
I am assuming you are using pure Hibernate as ORM; simply put, regardless of the status of your db, you have in your current Hibernate session different copies of the same row. To resolve this you can:
1) flush() the hibernate session after every writing operation on db (insert or update)
OR
2) In your update metod call merge() instead of saveOrUpdate()
Related
I'm trying to modify existing Java app (WildFly, Jboss, oracle) which currently working fine as using persistence-unit and EntityManager connect to Oracle database(using standalone.xml and persistence.xml). However, I need to create every time new connection to database for the user which calls new GET API Endpoint using credentials from the HttpHeaders. Currently, I'm creating new entitymanager object which session is commit, rollback nad close. Unfortunately time response for every call become higher and higher. There is warning about "PersistenceUnitUser" being already registered and memory usage constantly growing. So that is bad solution.
Is there any proper way to do it, which works witout any harms ?
P.S.
Currently app using standalone.xml and persistence.xml. And that is working fine. I'm calling java api endpoint using entity manager being connected as Admin user/pass but I need to create new connection using user/pass from the httpHeaders and call one sql statement to see proper results as ORACLE uses reserved word such us: 'user'. For instance : select * from table where create_usr = user. When done 'Main EntityManager will use data from it to continue some process.
Please see code example below :
#GET
#Path("/todo-list-enriched")
#Produces(MediaType.APPLICATION_JSON)
public Response getToDoListEnriched(#Context HttpHeaders httpHeaders, #QueryParam("skip") int elementNumber, #QueryParam("take") int pageSize, #QueryParam("orderby") String orderBy)
{
String userName = httpHeaders.getHeaderString(X_USER_NAME);
String userName = httpHeaders.getHeaderString(X_PASSWORD);
EntityManager entityManager = null;
try {
Map<String, String> persistenceMap = new HashMap<String, String>();
persistenceMap.put("hibernate.dialect","org.hibernate.dialect.Oracle8iDialect");
persistenceMap.put("hibernate.connection.username", asUserName);
persistenceMap.put("hibernate.connection.password", asPassword);
EntityManagerFactory emf = Persistence.createEntityManagerFactory("PersistenceUnitUser", persistenceMap);
entityManager = emf.createEntityManager();
if (!entityManager.getTransaction().isActive()) {
entityManager.getTransaction().begin();
}
-- Do some works as select, update, select
-- and after that
if (entityManager.getTransaction().isActive()) {
entityManager.getTransaction().commit();
}
}
catch (Exception ex)
{
if (entityManager != null && entityManager.getTransaction().isActive()) {
entityManager.getTransaction().rollback();
}
}
finally {
if (entityManager != null && entityManager.isOpen()) {
entityManager.close();
}
}
}
}
``
Best Regards
Marcin
You should define a connection pool and a datasource in the standalone.xml (cf. https://docs.wildfly.org/26.1/Admin_Guide.html#DataSource) and then use it in your persistence.xml and inject the EntitytManager in your rest service class (cf. https://docs.wildfly.org/26.1/Developer_Guide.html#entity-manager).
You may look at this example application: https://github.com/wildfly/quickstart/tree/main/todo-backend
What is the best way to handle errors when using Spring's Jpa Repository deleteById(Long id) method?
By default the deleteById(), checks to see if the ID has an existing row in the database, if it doesn't it throws a org.springframework.dao.EmptyResultDataAccessException because it expects a row size of 1.
I first tried to use my Exception Handler to pick up on this exception, which worked fine but the message exposes my package and class name to the user when Spring returns the error message.
#ExceptionHandler(EmptyResultDataAccessException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<RestApiError> handleEmptyResultDataAccessException(EmptyResultDataAccessException ex, HttpServletRequest request) {
RestApiError error = new RestApiError(HttpStatus.BAD_REQUEST, Map.of("message", ex.getMessage()), request.getRequestURI());
return new ResponseEntity<>(error, error.getHttpStatus());
}
ex.getMessage() returns:
No class net.demo.customerservice.model.CustomerLocation
entity with id 7 exists!
Instead I decided to catch EmptyResultDataAccessException, and then throw more useful exception and message where I call deleteById();
My current code:
public void delete(Long id) {
try {
repository.deleteById(id); // call Spring's Data JPA repository method deleteById
} catch (EmptyResultDataAccessException ex) {
throw new EntityNotFoundException("Location with ID: [" + id + "] was not found");
}
}
This works great, and returns a good error message to the user but it seems like a hack.
Is there any better way to handle the EmptyResultDataAccessException? I could also use the existsById() method before calling the delete method, but then I am using two queries.
Generally it's better to avoid catching exceptions throughout the code. If you can delegate exception handling to another class, you can handle errors consistently across your application in one place. You could use #ControllerAdvice for this:
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ExceptionHandler(EmptyResultDataAccessException.class)
public ResponseEntity<> handleRecordNotFound(EmptyResultDataAccessException ex) {
LOG.trace("Record not found: {}", ex.getMessage());
RestApiError error = new RestApiError(HttpStatus.BAD_REQUEST, Map.of("message", "Record not found"), request.getRequestURI());
return new ResponseEntity<>(error, error.getHttpStatus());
}
}
The client knows which entity it requested to delete, so there's no need to include the id in the error message returned to the client. You could log the message with he id to the log file.
All applications using hibernate need save and update to interact with the database. For save, I will check the existence for some criteria. If it doesn't exist, I will save. For update, I will check the existence and certain criteria to determine whether update or not. What is the best practice to do the check and save / update?
I am currently creating a separate function that open a session and search to determine the existence. The session open/close is very clumsy. I think there should be better way to do it.
public Event searchByDateAddress(Date _date, String _address, boolean _closeSess)
{
try
{
if(!session.isOpen())
{
session = HibernateUtil.getSessionFactory().openSession();
}
session.beginTransaction();
Criteria criteria = session.createCriteria(Event.class);
criteria.add(Restrictions.eq("date", _date));
criteria.add(Restrictions.eq("address", _address));
criteria.setFetchMode("organizerProfile", FetchMode.JOIN);
Event evt = (Event)criteria.uniqueResult();
if(_closeSess)
{
session.close();
}
if (evt==null)
{
LogUtils.logInfo("The event does not exist: " + _date + " " + _address);
return null;
}
else
{
return evt;
}
}
catch(Exception e)
{
LogUtils.logInfo(e.toString());
if(_closeSess)
{
session.close();
}
return null;
}
}
public EventDTO insertEvent(Event _event)
{
try
{
Event tmpEvent=new Event();
//Event not exists
if((tmpEvent=this.searchByDateAddress(_event.getDate(), _event.getAddress(), true))==null)
{
//insert
if(!session.isOpen())
{
session = HibernateUtil.getSessionFactory().openSession();
}
Transaction tx=session.beginTransaction();
long retOid=(Long)session.save(_event);
session.flush();
tx.commit();
session.close();
_event.setOid(retOid);
return new EventDTO(_event);
}
}
catch(Exception e)
{
e.printStackTrace();
session.close();
}
return new EventDTO();
}
Thanks
Regarding session handling, its best to handle in the API level, a leve before this "EventService". So you always assume that the session is available in the service methods.
Then the code will look more neet, avoiding session with the hibernate query.
Its also possible to avoid session handling at all using #Transactional attribute in the methods where you want session. This is possible using Spring. Look at : https://spring.io/guides/gs/managing-transactions/ for more info.
For checking for if a table has the data you with your where clause you may use count(*). So you dont need to get all the data at first.
Look at this example: hibernate native query, count
below is my hibernate code:
SessionFactory sessionFactory;
Session session = null;
LoginEntity user = null;
try {
sessionFactory = HibernateUtility.getSessionFactory();
session = sessionFactory.openSession();
session.beginTransaction();
user = session.get(LoginEntity.class, user_id);
System.out.println(user.getUserCountryMapping()); // if I remove this line I get error..
session.getTransaction().commit();
return user;
} catch (Exception e) {
Logger.getLogger(BulkActionMethods.class.getName()).log(Level.SEVERE, null, e);
} finally {
if (session != null) {
session.close();
}
}
I am facing a weird issue with my code, When I remove the System.out.println(user.getUserCountryMapping()); line I am getting HTTP Status 500 - Internal Server Error error on browser but when I write this line I am getting expacted JSON response on browser..
Please someone help me to understand this issue.
Without seeing the error or your entity mappings, it's hard to give a firm answer.
However, cases like this are almost always due to uninitialized lazy collections. The line:
System.out.println(user.getUserCountryMapping());
makes Hibernate fetch the data in that relationship. If you don't do this within a Hibernate session, and then try to rely on this relationship later you will get a LazyInitializationException, if not handled it will be a HTTP status 500.
Replace
System.out.println(user.getUserCountryMapping());
with
Hibernate.initialize(user.getUserCountryMapping());
Read more about lazy vs eager fetch type.
I am using hibernate in one of my java applications. The database runs on master slave hierarchy. In one of the classes I am trying to update an existing entity by first getting the entire entity from the master db & then updating it and saving it in the master db. But since this call can increase the load on the master database, I want all the reads to happen from the slave db only, and only updates on the master db. I cannot read from slave & write to master as hibernate somehow saves the session along with the entity and doesn't let you update in some other slavesession.
I came up with a solution:
First I will change the entity to json & then read the json to form a new entity which will hopefully solve the session issue. Below is the code:
private UserEntity getUserEntityForUpdate(UserData userData) {
UserEntity userEntity = (userEntity) sessionFactory.getCurrentSession().byId(userEntity.class).load(user.getId());
ObjectMapper mapper = new ObjectMapper();
String user1 = null;
try {
user1 = mapper.writeValueAsString(userEntity);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
userEntity obj = new UserEntity();
try {
obj = mapper.readValue(user1, userEntity.class);
} catch (IOException e) {
e.printStackTrace();
}
if (obj.getId() != null) {
userEntity.setId(user.getId());
}
obj.setEnabledBy(user.getEnabledBy() != null ? user.getEnabledBy() : "");
obj.setEnabledAt(user.getEnabledAt());
return obj;
}
Just wanted to know if this is a good approach or can I go for any better approach?
You can use session.evict(entity) to detach the entity from the session you have loaded it from and then you should be able to update it on the master.
The problem with this approach is that you can't really make transactions, You can't be sure that the entity has not changed in the meanwhile.