I am using Hibernate Criteria API to load the data from the DB, however when using multiselect and fetching the related entities with mapping #OneToOne, #ManyToOne & #ManyToMany, I am getting error.
Code to get the data
private Session getSession() {
return entityManager.unwrap(SessionImplementor.class);
}
#Override
public Account getGatewayAccount(Long appId, String accountNumber) {
Session session = getSession();
CriteriaBuilder criteria = session.getCriteriaBuilder();
CriteriaQuery<Account> query = criteria.createQuery(Account.class);
Root<Account> from = query.from(Account.class);
from.fetch(Account_.APP, JoinType.INNER);
query.multiselect(from.get(Account_.ID), from.get(Account_.ACCOUNT_NUMBER))
.where(criteria.equal(from.get(Account_.ACCOUNT_NUMBER), accountNumber),
criteria.equal(from.get(Account_.APP).get(App_.ID), appId));
try {
return session.createQuery(query)
.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
I have also tried using EntityGraph to lad the data like below
private Session getSession() {
return entityManager.unwrap(SessionImplementor.class);
}
#Override
public Account getGatewayAccount(Long appId, String accountNumber) {
Session session = getSession();
RootGraph<Account> entityGraph = session.createEntityGraph(Account.class);
entityGraph.addAttributeNodes("app");
CriteriaBuilder criteria = session.getCriteriaBuilder();
CriteriaQuery<Account> query = criteria.createQuery(Account.class);
Root<Account> from = query.from(Account.class);
query.multiselect(from.get(Account_.ID), from.get(Account_.ACCOUNT_NUMBER))
.where(criteria.equal(from.get(Account_.ACCOUNT_NUMBER), accountNumber),
criteria.equal(from.get(Account_.APP).get(App_.ID), appId));
try {
return session.createQuery(query)
.setHint("javax.persistence.fetchgraph", entityGraph)
.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
The exception that I am getting is
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.QueryException: query specified join fetching,
but the owner of the fetched association was not present in the select list
I am looking a way which can fetch the related objects with multiselect in select clause and get the data as POJO object, not like a Tuple.
What you want seems to be a constructor query
query.select(criteria.construct(Answer.class, from.get(Account_.ID), from.get(Account_.ACCOUNT_NUMBER))
This works by using a constructor on the defined class that accepts the two parameters define in the query, so bypasses all JPA hooks; What is returned is an unmanaged POJO. Since this isn't a managed entity fetch graphs will likely not have any meaning.
Otherwise, you can just return:
query.select(from)
This will return an Address instance that is managed and has all attributes described in your entityGraph loaded. Depending on your provider specifics, everything not defined in the entityGraph should be left as lazy and so unfetched.
Related
I'm trying to assign office for employees with same department. I'm gettin this error. What's the problem here ?
"org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations [update com.example.macademiaproject.model.Employee u set u.office = :office where u.department = :department]; nested exception is java.lang.IllegalStateException: org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations [update com.example.macademiaproject.model.Employee u set u.office = :office where u.department = :department]",
Query :
#Modifying
#Query("update Employee u set u.office = :office where u.department = :department")
List <Employee> assignOffice (#Param("office") String office,
#Param("department") String department);
ServiceImpl :
#Override
public List<Employee> assignOffice(String department,String office) {
List<Employee> employees = employeeRepository.assignOffice(office,department);
return employees;
}
Controller :
#GetMapping(path="/assign")
public ResponseEntity<List<Employee>> assignOffice(#RequestParam("office") String office,
#RequestParam("department") String department){
return ResponseEntity.ok(employeeService.assignOffice(office,department));
}
#Modifying(clearAutomatically = true)
can you try this annotation or you can take a look at this link.
Spring Boot Data JPA - Modifying update query - Refresh persistence context
When migrating from Hibernate Criteria api to CriteriaQuery I ran into a generic DAO for a abstract class that has a where on a common field but does a select on their id, even if the ids are totally different per class.
The old projection looks like this
criteria.setProjection(Projections.id());
Is there any way to do this in a similar way with CriteriaQuery?
Edit: Full criteria code
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(MyEntity.class);
detachedCriteria.add(Restrictions.in("accountID", accounts));
detachedCriteria.setProjection(Projections.id());
EntityManager em = ...;
Criteria criteria = detachedCriteria.getExecutableCriteria((Session) em.getDelegate());
List<Integer> list = criteria.list();
I just managed to find it on my own.
criteriaQuery.select(
root.get(entityRoot.getModel().getDeclaredId(int.class))
);
Combining answers:
https://stackoverflow.com/a/16911313
https://stackoverflow.com/a/47793003
I created this method:
public String getIdAttribute(EntityManager em, String fullClassName) {
Class<? extends Object> clazz = null;
try {
clazz = Class.forName(fullClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Metamodel m = em.getMetamodel();
IdentifiableType<T> of = (IdentifiableType<T>) m.managedType(clazz);
return of.getId(of.getIdType().getJavaType()).getName();
}
then I have the entity manager injected
#PersistenceContext
private EntityManager em;
I get the root entity primary key like that:
String rootFullClassName = root.getModel().getJavaType().getName();
String primaryKeyName = getIdAttribute(em, rootFullClassName);
and I get the primary keys referenced on attributes like that:
return (Specification<T>) (root, query, builder) -> {
Set<Attribute<? super T, ?>> attributes = root.getModel().getAttributes();
for (Attribute a: attributes) {
if(a.isAssociation()) {
Path rootJoinGetName = root.join(a.getName());
String referencedClassName = rootJoinGetName.getJavaType().getName();
String referencedPrimaryKey = getIdAttribute(em, referencedClassName);
//then I can use it to see if it is equal to a value (e.g
//filtering actors by movies with id = 1 - in
//this case referencedPrimaryKey is "id")
Predicate p = rootJoinGetName.get(referencedPrimaryKey).in(1);
}
}
}
In this way I don't need to know the type of the primary key/referenced key in advance as it can be derived through the Entity Manager Meta Model. The above code can be used with CriteriaQuery as well as Specifications.
I'm using Jersey and am expecitng a POST as an entity. However thst POST will also contain the UUID for one of its relationships:
Jersey Resource:
#POST
public WorkstationEntity save (WorkstationEntity workstationEntity) {
//WorkflowProcessEntity workflowProcessEntity = workflowProcessService.findByUUID();
workstationService.save(workstationEntity);
return workstationEntity;
}
How can I adjust the following mapping so it'll recognize the relationship and save correctly? Currently the workflow_process_id is NULL when it's saved and I have to query for the entity manually.
The JSON being posted is... {name: Workstation 1; workflow_process_id: 1}
private WorkflowProcessEntity workflowProcess;
#ManyToOne
#JoinColumn(name = "workflow_process_id", referencedColumnName = "id")
public WorkflowProcessEntity getWorkflowProcess() {
return workflowProcess;
}
public void setWorkflowProcess(WorkflowProcessEntity workflowProcess) {
this.workflowProcess = workflowProcess;
}
workstationService
#Transactional
public void save(WorkstationEntity workstationEntity) {
workstationRepository.save(workstationEntity);
}
Can you show code for workstationService? are you using Hibernatr or simple jdbc or any other orm tool?
I think inside workstationService.save(workstationEntity); you will need to attach workstationEntity to session (in case of Hibernate Hibernate Session). and then save it..
If I understand the problem it is that the returning json has a null id for the attached WorkstationProcessEntity id field. This is most likely a problem when you are trying to persist / merge the entity the transaction is not being committed before returning the detached entity. If you are using a persist make sure that you commit the transaction otherwise the id's will be null. Otherwise if you are using a merge this will commonly fix the problem.
protected T persist(T obj) {
EntityManager em = ThreadLocalPersistenceManager.getEntityManager();
EntityTransaction tx = em.getTransaction();
try {
if (! tx.isActive()) {
tx.begin();
}
em.persist(obj);
} finally {
if (!tx.getRollbackOnly()) {
tx.commit();
}
}
return obj;
}
The other likely cause is that your fetch is not set to eager so the datastore will only fetch the entity when it is accessed and by the time you are returning from the post the child entity is not attached. This is the most likely cause for your problem. What you should try is to access the workstation entitites getWorkflowProcess before closing the entity manager. Otherwise the attached entities will be null. Or add the FetchType.Eager annotation to fetch the child entities from the database when the parent is accessed.
Ok, so I have the following (abbreviated) 3 entity and HibernateUtil classes.
public class Tag {
#Id
BigDecimal id;
String tag
#ManyToMany( mappedBy="tags" )
List<Label> labels;
}
public class Label {
#Id
BigDecimal id;
String label;
#ManyToMany( targetEntity=Tag.class )
List<Tag> tags;
}
public class Data {
#Id
BigDecimal id;
BigDecimal data;
#ManyToOne
Label label;
}
public class HibernateUtil {
public static List pagedQuery(DetachedCriteria detachedCriteria, Integer start, Integer size) throws WebApplicationException {
Session session = getSession();
try {
Transaction transaction = session.beginTransaction();
List records = detachedCriteria.getExecutableCriteria(session)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.setFirstResult(start)
.setMaxResults(size)
.list();
transaction.commit();
return records;
} catch (Exception e) {
// Place Logger here...
throw new WebApplicationException(e);
} finally {
session.close();
}
}
}
The issue I have is that when I try to query the Data class with the HibernateUtil.pagedQuery( detatchedCriteria, start, size ), my result list doesn't match the size parameter. I have found that the reason for this is the way hibernate builds the query to include the tags (Data.Label.Tags).
For instance, when a Label has more than one associated Tags the result list for the Data object subquery used in the complete paginated query would look like the following (I found this by parsing the sql Hibernate spits out to the console)
Data-1;Label:Tag-1
Data-1;Label;Tag-2
Data-2;Label;Tag-1
Data-2;Label;Tag-2
etc...
If I were to call this with size=3, then the returned result set would be
Data-1;Label:Tag-1
Data-1;Label;Tag-2
Data-2;Label;Tag-1
However, Hibernate would then group the first two rows together (since they're the same Data object), and my returned List object would have a size of 2 (Data-1 & Data-2)
I attempted to replace the setResultTransformer method with a Projection approach that I found through Google, but that then only returned the id's of the Data objects.
Does anyone have any advice for me? I'm not sure where to go from here...
You are facing a common problem paginating with hibernate. The resultTransformer is applied in the "Java" side, so the pagination has already been made on the DB side.
The simplest (maybe not the most optimized) is to do two queries, one with the projection and pagination (like the one you already did) and another using the projection id's. Here is an example:
//get the projection
Criteria criteria = factory.getCurrentSession().createCriteria(getEntityClass());
criteria.setProjection(Projections.distinct((Projections.projectionList().add(Projections.id()).add(Projections.property("name")))));
//paginate the results
criteria.setMaxResults(pageSize);
criteria.setFirstResult(first);
List<Object[]> idList = criteria.list();
//get the id's from the projection
List<Long> longList = new ArrayList<Long>();
for (Object[] long1 : idList) {
Object[] record = long1;
longList.add((Long) record[0]);
}
if (longList.size() > 0) {
//get all the id's corresponding to the projection,
//then apply distinct root entity
criteria = factory.getCurrentSession().createCriteria(getEntityClass());
criteria.add(Restrictions.in("id", longList));
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
} else {
//no results, so let's ommit the second query to the DB
return new ArrayList<E>();
}
return criteria.list();
im having javax.jdo.JDODetachedFieldAccessException when i want to, after retrieve all of my Entites as a List in my DAO implementation, ask for one atrribute object from my Entity.
public List<T> findAll() {
this.entityManager = SingletonEntityManagerFactory.get().createEntityManager();
EntityTransaction tx = this.entityManager.getTransaction();
try {
tx.begin();
return this.entityManager.createQuery(
"select f from " + clazz.getName() + " as f").getResultList();
}finally {
tx.commit();
if (tx.isActive()) {
tx.rollback();
}
this.entityManager.close();
}
}
for instance, supposing T has a property of class A that is already an Entity persisted, i can't get A after having List
But i don't have this problem if I only look for a single Entity by Id. I obtain my entity and I can ask without problems for its attribute objects already persisted
public T getById(final Key id) {
return getEntityManager().find(clazz, id);
}
now i can do
A a= t.getA();
How can I write my implementation of findAll() avoiding this error? maybe another Component instead of EntityManager? How can i make it generic, and not having to implement specific code for specific type of entities?
What you do there doesn't make sure the field is loaded before leaving that method, so either access it, or make sure it is fetched by default.