I've just begun working with JPA and hibernate, and I have been able to insert query and update on one table, but I cannot seem to delete an entity. Even after I attach the entity with the merge function, the program insists it is detached. Here is the relevant code:
public User getUserByEmail(String email) throws DAOException{
User user = null;
EntityManager em = null; //declare here for use in finally block
try{
//get em for use
EntityManagerFactory factory = JPAUtil.getEntityManagerFactory();
em = factory.createEntityManager();
//setup return type as user
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> criteriaQuery = cb.createQuery(User.class);
Root<User> userRoot = criteriaQuery.from(User.class);
//populate the where clause
criteriaQuery.where(cb.equal(userRoot.get("email"), cb.parameter(String.class, "email")));
Query query = em.createQuery(criteriaQuery);
query.setParameter("email", email);
//actually run the query on db
user = (User) query.getSingleResult();
}catch(Exception ex){
DAOException dE = new DAOException(2, "getUserByEmail failed", ex);
//TODO log
throw dE;
}
//cleanup
finally{
if(em != null && em.isOpen()){
em.close();
}
}
return user; //if not found, will still be null
}
#Override
/*
* (non-Javadoc)
* #see dao.IUserDAO#deleteUser(java.lang.String)
*/
public void deleteUser(String email) throws DAOException {
EntityManagerFactory entityManagerFactory;
EntityManager em = null;
EntityTransaction trans = null;
try{
//search user on email
User user = getUserByEmail(email);
//check we found user with specified email
if(user != null){
//setup for interaction with database
entityManagerFactory = JPAUtil.getEntityManagerFactory();
em = entityManagerFactory.createEntityManager();
//alterations of db must occur in scope of transaction
trans = em.getTransaction();
trans.begin();
em.merge(user); //TODO update find to take transaction as parameter
em.remove(user);
trans.commit();
//explicitly flush here
em.flush();
}else{
//TODO we didn't find the user
}
}catch(Exception ex){
DAOException dE = new DAOException(6, "delete failed", ex);
//TODO log
trans.rollback();
throw dE;
}finally{
if(em != null && em.isOpen()){
em.close();
}
}
}
And here is the output from the eclipse console:
"Removing a deattached instance of User#1".
Could someone explain why this bean is still deattached after I explicitly called merge? Also, how would I fix this problem? Thanks.
The content of the specified detached entity object is copied into an
existing managed entity object with the same identity (i.e. same type
and primary key). If the EntityManager does not manage such an entity
object yet a new managed entity object is constructed. The detached
object itself, however, remains unchanged and detached.
Even after merge the detached object itself remains detached, merge will return the new created merged entity
user = em.merge(user); //TODO update find to take transaction as parameter
em.remove(user);
Related
i am trying to persist multiple entities to database. but i need to roll back all inserts if one of them faces an exception. how can i do that?
here is what i did:
public class RoleCreationApplyService extends AbstractEntityProxy implements EntityProxy {
#Inject
#Override
public void setEntityManager(EntityManager em) {
super.entityManager = em;
}
#Resource
UserTransaction utx;
public Object acceptAppliedRole(String applyId, Role parentRole, SecurityContext securityContext) throws Exception {
utx.begin();
try {
FilterWrapper filter = FilterWrapper.createWrapperWithFilter("id", Filter.Operator._EQUAL, applyId);
RoleCreationApply roleCreationApply = (RoleCreationApply) getByFilter(RoleCreationApply.class, filter);
Role appliedRole = new Role();
appliedRole.setRoleUniqueName(roleCreationApply.getRoleName());
appliedRole.setRoleName(roleCreationApply.getRoleName());
appliedRole.setRoleDescription(roleCreationApply.getRoleDescription());
appliedRole.setRoleDisplayName(roleCreationApply.getRoleDisplayName());
appliedRole.setCreationTime(new Date());
appliedRole.setCreatedBy(securityContext.getUserPrincipal().getName());
Role childRole = (Role) save(appliedRole);
parentRole.setCreationTime(new Date());
parentRole.setCreatedBy(securityContext.getUserPrincipal().getName());
parentRole = (Role) save(parentRole);
RoleRelation roleRelation = new RoleRelation();
roleRelation.setParentRole(parentRole);
roleRelation.setChildRole(childRole);
RoleRelation savedRoleRelation = (RoleRelation) save(roleRelation);
PostRoleRelation postRoleRelation = new PostRoleRelation();
postRoleRelation.setPost(roleCreationApply.getPost());
postRoleRelation.setRoleRelation(savedRoleRelation);
ir.tamin.framework.domain.Resource result = save(postRoleRelation);
utx.commit();
return result;
} catch (Exception e) {
utx.rollback();
throw new Exception(e.getMessage());
}
}
}
and this is save method in AbstractEntityProxy class:
#Override
#ProxyMethod
public Resource save(Resource clientObject) throws ProxyProcessingException {
checkRelationShips((Entity) clientObject, Method.SAVE, OneToOne.class, ManyToOne.class);
try {
entityManager.persist(clientObject);
} catch (PersistenceException e) {
throw new ResourceAlreadyExistsException(e);
}
return clientObject;
}
but when an exception occures for example Unique Constraint Violated and it goes to catch block, when trying to execute utx.rollback() it complains transaction does not exist and so some entities will persist. but i want all to roll back if one fails.
PS: i don't want to use plain JDBC. what is JPA approach?
I am currently working on a Servlet which saves songs. I am using hsqldb with JPA and my problem is that on initializing the DB I have to load 10 songs from a JSON file. But as long as I use #GeneratedValue in my song Entity it will just load 5 of the 10 songs.
Here is my code where I access the JSON file.
#Override
public void init() {
emf = Persistence.createEntityManagerFactory("NewPersistenceUnit");
// Problem with loading just half the json
loadSongsFromJSON("songs.json");
System.out.println("DB initialized");
}
private void loadSongsFromJSON(String filename) {
List<Song> songsFromJSON = null;
EntityManager em = null;
try {
ObjectMapper objectMapper = new ObjectMapper();
InputStream is = getClass().getClassLoader().getResourceAsStream(filename);
songsFromJSON = objectMapper.readValue(is, new TypeReference<List<Song>>() {
});
} catch (IOException e) {
e.printStackTrace();
}
if (songsFromJSON != null) {
//for testing
System.out.println("ALL SONGS IN INIT" + System.lineSeparator());
for (Song s : songsFromJSON) {
em = emf.createEntityManager();
em.getTransaction().begin();
//em.persist(s);
em.merge(s);
//for testing
System.out.println("ADDED " + s);
em.getTransaction().commit();
em.close();
}
}
}
All 10 Songs are definitly in the list since:
System.out.println("ADDED " + s);
is printing out all of them.
I also need to use #GeneratedValue so deleting it isn't an option.
Ok I solved the problem.
Tomislav's answer helped me solving it.
The problem was, that in my JSON the songs have a set ID. So I set all ID's to null and then persisted them normally with
s.setId(null);
em.persist(s) //instead of em.merge(s)
Have a list of data need to be saved. Before the save had to delete the existing data and save the new data.
If any of the delete & save is failed that transaction need to roll back, rest of the delete & save transaction should continue.
public LabResResponse saveLabResult(List<LabResInvstResultDto> invstResults) {
LabResResponse labResResponse = new LabResResponse();
List<Long> relInvstid = new ArrayList<Long>();
try{
if(invstResults != null){
List<LabResInvstResult> labResInvstResults = mapper.mapAsList(invstResults, LabResInvstResult.class);
for(LabResInvstResult dto: labResInvstResults){
if(dto != null){
//delete all child records before save.
deleteResult(dto, relInvstid);
}
}
}
labResResponse.setRelInvstids(relInvstid);
}catch(Exception e){
e.printStackTrace();
}
return labResResponse;
}
Here new transaction will added for each delete & save
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = { Exception.class })
private void deleteResult(LabResInvstResult dto, List<Long> relInvstid) {
try{
labResultRepo.deleteById(dto.getId());
LabResInvstResult result = labResultRepo.save(dto);
}catch(Exception e){
e.printStackTrace();
}
}
On delete it throws an exception "Caused by: javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call"
I can solve this by adding a #Transactional for public LabResResponse saveLabResult(List invstResults) method.
But my intial usecase will not work this will roll back entire list of transaction.
Here are two problems.
The first problem is that you call the "real" deleteResult method of the class. When Spring sees #Transactional it creates a proxy object with transactional behavior. Unless you're using AspectJ it won't change the class itself but create a new one, proxy. So when you autowire this bean you will be able use proxy's method that runs transaction related logic. But in your case you're referencing to the method of the class, not proxy.
The second problem is that Spring (again if AspectJ is not used) can't proxy non-public methods.
Summary: make the deleteResult method public somehow and use proxied one. As a suggestion, use another component with deleteResult there.
You are catching exception out of for loop, while your requirement says you want to continue the loop for other objects in list.
Put your try/catch block with-in loop. It should work fine
public LabResResponse saveLabResult(List<LabResInvstResultDto> invstResults) {
LabResResponse labResResponse = new LabResResponse();
List<Long> relInvstid = new ArrayList<Long>();
try{
if(invstResults != null){
List<LabResInvstResult> labResInvstResults = mapper.mapAsList(invstResults, LabResInvstResult.class);
for(LabResInvstResult dto: labResInvstResults){
if(dto != null){
//delete all child records before save.
try {
deleteResult(dto, relInvstid);
} catch(Exception e){
e.printStackTrace();
}
}
}
}
labResResponse.setRelInvstids(relInvstid);
}catch(Exception e){
e.printStackTrace();
}
return labResResponse;
}
I am working rest services with spring and hibernate,for updating employee data using below code, but when run I got below error
{
"code": 0,
"message": "org.hibernate.HibernateException: illegally attempted to associate a proxy with two open Sessions"
}
DataDaoImpl.java
#Override
public Employee getEntityById(long id) throws Exception {
session = sessionFactory.openSession();
Employee employee = (Employee) session.load(Employee.class,
new Long(id));
tx = session.getTransaction();
session.beginTransaction();
tx.commit();
return employee;
}
RestController.jav
#RequestMapping(value = "/save/{id}", method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody
Status saveUser(#PathVariable("id") long id,#RequestBody Employee employee) {
Employee employeeupdate = null;
try {
employeeupdate = dataServices.getEntityById(id);
employeeupdate.setFirstName(employee.getFirstName());
employeeupdate.setLastName(employee.getLastName());
employeeupdate.setEmail(employee.getEmail());
employeeupdate.setPhone(employee.getPhone());
dataServices.updateEntity(employeeupdate);
return new Status(1, "Employee updated Successfully !");
} catch (Exception e) {
// e.printStackTrace();
return new Status(0, e.toString());
}
}
#Override
public boolean updateEntity(Employee employeeupdate) throws Exception {
session = sessionFactory.openSession();
tx = session.beginTransaction();
session.update(employeeupdate);
tx.commit();
session.close();
return false;
}
What mistake have I done here?
In the getEntityById(...) method the session is not closed. Close the session using session.close(); before returning the employee and try.
Don't open 2 sessions. Open just one and use it.
place the
session = sessionFactory.openSession();
tx = session.beginTransaction();
In the beginning of saveUser() and pass the session/transaction to the methods. Actually you don't need transaction in the getEntityById() - you change nothing.
Appart of #StanislavL answer, there are some errors in your code getEntityById() should be (you need close a session and place a transaction above load()).
#Override
public Employee getEntityById(long id) throws Exception {
session = sessionFactory.openSession();
tx = session.getTransaction();
session.beginTransaction();
Employee employee = (Employee) session.load(Employee.class,
new Long(id));
tx.commit();
session.close();
return employee;
}
But this variant is not very correct too, you should catch an exception use a finally block and rollback a transaction. The best way is using this pattern doInTransaction().
Update
It is better to get an entity this way
Employee employee = (Employee) session.get(Employee.class, id);
In short, you should not create session in your DAO.
Given that you are using Spring, you should avoid manually creating the Session/Transaction. Please make use of Spring to manage the transaction and session creation, by relying on Spring's transaction control and things like LocalEntityManagerFactoryBean
I'm not sure if I should be setting the object to the result of the merge and then return that, or just do a merge. I'm using the technique in the first block below but I am sometimes losing data and I don't know why.
#Override
public T save(T object) {
EntityManager em = null;
EntityTransaction tx = null;
try {
em = getEntityManager();
tx = em.getTransaction();
tx.begin();
object = em.merge(object);
tx.commit();
return object;
} catch (RuntimeException e) {
log.severe(e.getMessage());
if (tx != null && tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
if (em != null && em.isOpen()) {
em.close();
}
}
}
Or should I do a merge this way?
#Override
public void save(T object) {
EntityManager em = null;
EntityTransaction tx = null;
try {
em = getEntityManager();
tx = em.getTransaction();
tx.begin();
object = em.merge(object);
tx.commit();
} catch (RuntimeException e) {
log.severe(e.getMessage());
if (tx != null && tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
if (em != null && em.isOpen()) {
em.close();
}
}
}
After the top method is used the object that was passed in may get modified and then another save is done. This object has one to one and one to many relationships with other entities. Sometimes the data in one of the one-to-many entities get lost or isn't saved. But I can't reliably reproduce the problem.
Do you really need to merge at all, merge is normally used when serializing objects, if you don't need to serialize objects, you may not need to merge. If you query the objects from the same EntityManager, you just need to change them and then commit.